From cf61641195b87517716f8c2abfc376d2dc823632 Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Tue, 8 Nov 2022 15:56:13 -0500 Subject: [PATCH 1/7] build: making admin optional Signed-off-by: Alyssa Wilk --- bazel/BUILD | 5 + bazel/README.md | 1 + bazel/envoy_build_system.bzl | 6 +- bazel/envoy_internal.bzl | 3 +- bazel/envoy_select.bzl | 7 ++ ci/do_ci.sh | 4 +- configs/BUILD | 3 + envoy/server/admin.h | 5 + envoy/server/factory_context.h | 4 +- envoy/server/instance.h | 4 +- envoy/server/transport_socket_config.h | 4 +- source/common/config/config_provider_impl.cc | 16 +-- source/common/config/config_provider_impl.h | 2 +- .../route_config_provider_manager_impl.h | 2 +- .../rds/route_config_provider_manager.cc | 18 +-- .../rds/route_config_provider_manager.h | 2 +- source/common/router/rds_impl.cc | 2 +- source/common/router/rds_impl.h | 2 +- source/common/router/scoped_rds.h | 3 +- source/common/secret/secret_manager_impl.cc | 14 ++- source/common/secret/secret_manager_impl.h | 2 +- source/common/upstream/cluster_factory_impl.h | 2 +- .../common/upstream/cluster_manager_impl.cc | 13 ++- source/common/upstream/cluster_manager_impl.h | 6 +- source/common/upstream/upstream_impl.cc | 4 +- source/exe/main_common.cc | 4 +- source/extensions/common/tap/BUILD | 9 +- source/extensions/common/tap/admin.cc | 6 +- source/extensions/common/tap/admin.h | 4 +- .../common/tap/extension_config_base.cc | 2 +- .../common/tap/extension_config_base.h | 2 +- source/extensions/filters/http/tap/BUILD | 9 +- .../extensions/filters/http/tap/tap_filter.cc | 2 +- .../extensions/filters/http/tap/tap_filter.h | 5 +- .../extensions/stat_sinks/hystrix/hystrix.cc | 11 +- source/extensions/transport_sockets/tap/BUILD | 9 +- .../extensions/transport_sockets/tap/tap.cc | 4 +- source/extensions/transport_sockets/tap/tap.h | 4 +- source/server/admin/BUILD | 13 ++- source/server/admin/admin.h | 2 +- source/server/config_validation/BUILD | 1 + source/server/config_validation/admin.h | 1 + .../config_validation/cluster_manager.h | 2 +- source/server/config_validation/server.cc | 2 +- source/server/config_validation/server.h | 5 +- source/server/factory_context_base_impl.h | 6 +- source/server/filter_chain_manager_impl.cc | 4 +- source/server/filter_chain_manager_impl.h | 4 +- source/server/listener_impl.cc | 6 +- source/server/listener_impl.h | 4 +- source/server/listener_manager_impl.cc | 12 +- source/server/server.cc | 45 ++++++-- source/server/server.h | 9 +- source/server/transport_socket_config_impl.h | 2 +- test/common/router/rds_impl_test.cc | 5 +- test/common/router/vhds_test.cc | 4 +- test/exe/BUILD | 3 +- test/extensions/common/tap/BUILD | 3 +- .../http/common/fuzz/uber_per_filter.cc | 3 +- .../health_check_integration_test.cc | 2 + test/extensions/filters/http/tap/BUILD | 7 +- .../tls_inspector_integration_test.cc | 1 + .../filters/network/common/fuzz/utils/fakes.h | 2 +- ...reserve_case_formatter_integration_test.cc | 2 + test/extensions/stats_sinks/hystrix/BUILD | 3 +- .../transport_sockets/tls/integration/BUILD | 12 +- .../tls/integration/ssl_integration_test.cc | 16 ++- test/integration/BUILD | 104 +++++++++++------- test/integration/ads_integration.cc | 6 +- test/integration/base_integration_test.cc | 19 +++- test/integration/base_integration_test.h | 8 ++ test/integration/cx_limit_integration_test.cc | 1 + test/integration/integration_admin_test.cc | 2 +- test/integration/integration_test.cc | 3 + test/integration/server.cc | 4 +- test/mocks/server/admin.h | 1 + test/mocks/server/factory_context.cc | 2 +- test/mocks/server/factory_context.h | 2 +- test/mocks/server/instance.cc | 4 +- test/mocks/server/instance.h | 6 +- test/mocks/server/listener_factory_context.cc | 2 +- test/mocks/server/listener_factory_context.h | 2 +- .../server/transport_socket_factory_context.h | 4 +- test/server/BUILD | 3 +- test/server/admin/BUILD | 44 ++++---- test/server/config_validation/xds_fuzz.cc | 4 +- .../server/config_validation/xds_fuzz_test.cc | 6 +- test/server/server_test.cc | 14 +-- 88 files changed, 396 insertions(+), 231 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index 146f74bdd5221..c0a2d600da836 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -335,6 +335,11 @@ config_setting( values = {"define": "admin_html=disabled"}, ) +config_setting( + name = "disable_admin_functionality", + values = {"define": "admin_functionality=disabled"}, +) + config_setting( name = "disable_hot_restart_setting", values = {"define": "hot_restart=disabled"}, diff --git a/bazel/README.md b/bazel/README.md index 2d238c7cc34d9..72ea22f5a30f1 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -689,6 +689,7 @@ The following optional features can be disabled on the Bazel build command-line: * http3/quic with --//bazel:http3=False * autolinking libraries with --define=library_autolink=disabled * admin HTML home page with `--define=admin_html=disabled` +* admin functionlity with `--define=admin_functionaity=disabled` ## Enabling optional features diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index f87903151df8d..c595c4fa4f944 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -18,6 +18,7 @@ load( load(":envoy_pch.bzl", _envoy_pch_library = "envoy_pch_library") load( ":envoy_select.bzl", + _envoy_select_admin_functionality = "envoy_select_admin_functionality", _envoy_select_admin_html = "envoy_select_admin_html", _envoy_select_admin_no_html = "envoy_select_admin_no_html", _envoy_select_boringssl = "envoy_select_boringssl", @@ -51,8 +52,8 @@ load( ) load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") -def envoy_package(): - native.package(default_visibility = ["//visibility:public"]) +def envoy_package(default_visibility = ["//visibility:public"]): + native.package(default_visibility = default_visibility) def envoy_extension_package(enabled_default = True, default_visibility = EXTENSION_PACKAGE_VISIBILITY): native.package(default_visibility = default_visibility) @@ -210,6 +211,7 @@ def envoy_google_grpc_external_deps(): # Select wrappers (from envoy_select.bzl) envoy_select_admin_html = _envoy_select_admin_html envoy_select_admin_no_html = _envoy_select_admin_no_html +envoy_select_admin_functionality = _envoy_select_admin_functionality envoy_select_boringssl = _envoy_select_boringssl envoy_select_google_grpc = _envoy_select_google_grpc envoy_select_enable_http3 = _envoy_select_enable_http3 diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index 76bce7f05a71f..d5d2c47f1acb4 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -1,6 +1,6 @@ # DO NOT LOAD THIS FILE. Targets from this file should be considered private # and not used outside of the @envoy//bazel package. -load(":envoy_select.bzl", "envoy_select_admin_html", "envoy_select_enable_http3", "envoy_select_google_grpc", "envoy_select_hot_restart") +load(":envoy_select.bzl", "envoy_select_admin_functionality", "envoy_select_admin_html", "envoy_select_enable_http3", "envoy_select_google_grpc", "envoy_select_hot_restart") # Compute the final copts based on various options. def envoy_copts(repository, test = False): @@ -123,6 +123,7 @@ def envoy_copts(repository, test = False): "//conditions:default": [], }) + envoy_select_hot_restart(["-DENVOY_HOT_RESTART"], repository) + \ envoy_select_admin_html(["-DENVOY_ADMIN_HTML"], repository) + \ + envoy_select_admin_functionality(["-DENVOY_ADMIN_FUNCTIONALITY"], repository) + \ envoy_select_enable_http3(["-DENVOY_ENABLE_QUIC"], repository) + \ _envoy_select_perf_annotation(["-DENVOY_PERF_ANNOTATION"]) + \ _envoy_select_perfetto(["-DENVOY_PERFETTO"]) + \ diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 50c34b8388b73..77221e8ee3416 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -32,6 +32,13 @@ def envoy_select_admin_html(xs, repository = ""): "//conditions:default": xs, }) +# Selects the given values if admin functionality is enabled in the current build. +def envoy_select_admin_functionality(xs, repository = ""): + return select({ + repository + "//bazel:disable_admin_functionality": [], + "//conditions:default": xs, + }) + def envoy_select_admin_no_html(xs, repository = ""): return select({ repository + "//bazel:disable_admin_html": xs, diff --git a/ci/do_ci.sh b/ci/do_ci.sh index b179df0c8e0a8..f8f2754a43de0 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -396,8 +396,8 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then echo "Building and testing with wasm=wamr: ${TEST_TARGETS[*]}" bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wamr "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only - echo "Building and testing with wasm=wasmtime: ${TEST_TARGETS[*]}" - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wasmtime "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only + echo "Building and testing with wasm=wasmtime: and admin_functionality disabled ${TEST_TARGETS[*]}" + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wasmtime --define admin_html=disabled --define admin_functionality=disabled "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only diff --git a/configs/BUILD b/configs/BUILD index 0c8ef1ecf395c..43d71e05ac101 100644 --- a/configs/BUILD +++ b/configs/BUILD @@ -34,14 +34,17 @@ filegroup( "using_deprecated_config.yaml", "**/*.template.yaml", "freebind/freebind.yaml", + "envoy-tap-config.yaml", "envoy-demo.yaml", ], ) + select({ "//bazel:apple": ["envoy-demo.yaml"], "//bazel:windows_x86_64": [], + "//bazel:disable_admin_functionality": [], "//conditions:default": [ "envoy-demo.yaml", "freebind/freebind.yaml", + "envoy-tap-config.yaml", ], }), ) diff --git a/envoy/server/admin.h b/envoy/server/admin.h index 658f6f631f9b1..c9d84a25953b1 100644 --- a/envoy/server/admin.h +++ b/envoy/server/admin.h @@ -279,6 +279,11 @@ class Admin { */ static RequestPtr makeStaticTextRequest(absl::string_view response_text, Http::Code code); static RequestPtr makeStaticTextRequest(Buffer::Instance& response_text, Http::Code code); + + /** + * Closes the listening socket for the admin. + */ + virtual void closeSocket() PURE; }; } // namespace Server diff --git a/envoy/server/factory_context.h b/envoy/server/factory_context.h index bb647c64bf209..cc8d3b3c9bb8d 100644 --- a/envoy/server/factory_context.h +++ b/envoy/server/factory_context.h @@ -66,9 +66,9 @@ class FactoryContextBase { virtual const LocalInfo::LocalInfo& localInfo() const PURE; /** - * @return Server::Admin& the server's global admin HTTP endpoint. + * @return OptRef the global HTTP admin endpoint for the server. */ - virtual Server::Admin& admin() PURE; + virtual OptRef admin() PURE; /** * @return Runtime::Loader& the singleton runtime loader for the server. diff --git a/envoy/server/instance.h b/envoy/server/instance.h index 575c156c90619..f3fdd98be6baf 100644 --- a/envoy/server/instance.h +++ b/envoy/server/instance.h @@ -47,9 +47,9 @@ class Instance { virtual ~Instance() = default; /** - * @return Admin& the global HTTP admin endpoint for the server. + * @return OptRef the global HTTP admin endpoint for the server. */ - virtual Admin& admin() PURE; + virtual OptRef admin() PURE; /** * @return Api::Api& the API used by the server. diff --git a/envoy/server/transport_socket_config.h b/envoy/server/transport_socket_config.h index 9b3d5e3ad1c47..64401ca61a498 100644 --- a/envoy/server/transport_socket_config.h +++ b/envoy/server/transport_socket_config.h @@ -30,9 +30,9 @@ class TransportSocketFactoryContext { virtual ~TransportSocketFactoryContext() = default; /** - * @return Server::Admin& the server's admin interface. + * @return OptRef the global HTTP admin endpoint for the server. */ - virtual Server::Admin& admin() PURE; + virtual OptRef admin() PURE; /** * @return Ssl::ContextManager& the SSL context manager. diff --git a/source/common/config/config_provider_impl.cc b/source/common/config/config_provider_impl.cc index b25115b40314d..fc58e02091fd7 100644 --- a/source/common/config/config_provider_impl.cc +++ b/source/common/config/config_provider_impl.cc @@ -50,14 +50,16 @@ bool ConfigSubscriptionInstance::checkAndApplyConfigUpdate(const Protobuf::Messa return true; } -ConfigProviderManagerImplBase::ConfigProviderManagerImplBase(Server::Admin& admin, +ConfigProviderManagerImplBase::ConfigProviderManagerImplBase(OptRef admin, const std::string& config_name) { - config_tracker_entry_ = admin.getConfigTracker().add( - config_name, - [this](const Matchers::StringMatcher& name_matcher) { return dumpConfigs(name_matcher); }); - // ConfigTracker keys must be unique. We are asserting that no one has stolen the key - // from us, since the returned entry will be nullptr if the key already exists. - RELEASE_ASSERT(config_tracker_entry_, ""); + if (admin.has_value()) { + config_tracker_entry_ = admin->getConfigTracker().add( + config_name, + [this](const Matchers::StringMatcher& name_matcher) { return dumpConfigs(name_matcher); }); + // ConfigTracker keys must be unique. We are asserting that no one has stolen the key + // from us, since the returned entry will be nullptr if the key already exists. + RELEASE_ASSERT(config_tracker_entry_, ""); + } } const ConfigProviderManagerImplBase::ConfigProviderSet& diff --git a/source/common/config/config_provider_impl.h b/source/common/config/config_provider_impl.h index 7cbdfd4955128..9c5d844a4b5da 100644 --- a/source/common/config/config_provider_impl.h +++ b/source/common/config/config_provider_impl.h @@ -394,7 +394,7 @@ class ConfigProviderManagerImplBase : public ConfigProviderManager, public Singl using ConfigSubscriptionMap = absl::node_hash_map>; - ConfigProviderManagerImplBase(Server::Admin& admin, const std::string& config_name); + ConfigProviderManagerImplBase(OptRef admin, const std::string& config_name); const ConfigSubscriptionMap& configSubscriptions() const { return config_subscriptions_; } diff --git a/source/common/rds/common/route_config_provider_manager_impl.h b/source/common/rds/common/route_config_provider_manager_impl.h index 291f9ad50b28e..9b1f27c27fbae 100644 --- a/source/common/rds/common/route_config_provider_manager_impl.h +++ b/source/common/rds/common/route_config_provider_manager_impl.h @@ -36,7 +36,7 @@ template , public Singleton::Instance { public: - RouteConfigProviderManagerImpl(Server::Admin& admin) + RouteConfigProviderManagerImpl(OptRef admin) : manager_(admin, absl::AsciiStrToLower(getRdsName()) + "_routes", proto_traits_) {} // RouteConfigProviderManager diff --git a/source/common/rds/route_config_provider_manager.cc b/source/common/rds/route_config_provider_manager.cc index cdc9d05e4205f..f31ee868eaf9a 100644 --- a/source/common/rds/route_config_provider_manager.cc +++ b/source/common/rds/route_config_provider_manager.cc @@ -5,16 +5,18 @@ namespace Envoy { namespace Rds { -RouteConfigProviderManager::RouteConfigProviderManager(Server::Admin& admin, +RouteConfigProviderManager::RouteConfigProviderManager(OptRef admin, const std::string& config_tracker_key, ProtoTraits& proto_traits) - : config_tracker_entry_(admin.getConfigTracker().add( - config_tracker_key, - [this](const Matchers::StringMatcher& matcher) { return dumpRouteConfigs(matcher); })), - proto_traits_(proto_traits) { - // ConfigTracker keys must be unique. We are asserting that no one has stolen the "routes" key - // from us, since the returned entry will be nullptr if the key already exists. - RELEASE_ASSERT(config_tracker_entry_, ""); + : proto_traits_(proto_traits) { + if (admin.has_value()) { + config_tracker_entry_ = admin->getConfigTracker().add( + config_tracker_key, + [this](const Matchers::StringMatcher& matcher) { return dumpRouteConfigs(matcher); }); + // ConfigTracker keys must be unique. We are asserting that no one has stolen the "routes" key + // from us, since the returned entry will be nullptr if the key already exists. + RELEASE_ASSERT(config_tracker_entry_, ""); + } } void RouteConfigProviderManager::eraseStaticProvider(RouteConfigProvider* provider) { diff --git a/source/common/rds/route_config_provider_manager.h b/source/common/rds/route_config_provider_manager.h index e94bb36b06ad4..c527ae60f8194 100644 --- a/source/common/rds/route_config_provider_manager.h +++ b/source/common/rds/route_config_provider_manager.h @@ -21,7 +21,7 @@ namespace Rds { class RouteConfigProviderManager { public: - RouteConfigProviderManager(Server::Admin& admin, const std::string& config_tracker_key, + RouteConfigProviderManager(OptRef admin, const std::string& config_tracker_key, ProtoTraits& proto_traits); void eraseStaticProvider(RouteConfigProvider* provider); diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index 64ff3bfb1a4f6..b4f5dde2ed5d3 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -219,7 +219,7 @@ void RdsRouteConfigProviderImpl::requestVirtualHostsUpdate( }); } -RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(Server::Admin& admin) +RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(OptRef admin) : manager_(admin, "routes", proto_traits_) {} Router::RouteConfigProviderSharedPtr RouteConfigProviderManagerImpl::createRdsRouteConfigProvider( diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index f98e51f2b912f..340bfe6e05758 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -179,7 +179,7 @@ using ProtoTraitsImpl = class RouteConfigProviderManagerImpl : public RouteConfigProviderManager, public Singleton::Instance { public: - RouteConfigProviderManagerImpl(Server::Admin& admin); + RouteConfigProviderManagerImpl(OptRef admin); std::unique_ptr dumpRouteConfigs(const Matchers::StringMatcher& name_matcher) const { diff --git a/source/common/router/scoped_rds.h b/source/common/router/scoped_rds.h index 4e275ec260ac6..3e0947c4fef40 100644 --- a/source/common/router/scoped_rds.h +++ b/source/common/router/scoped_rds.h @@ -268,7 +268,8 @@ class ScopedRdsConfigProvider : public Envoy::Config::MutableConfigProviderCommo class ScopedRoutesConfigProviderManager : public Envoy::Config::ConfigProviderManagerImplBase { public: ScopedRoutesConfigProviderManager( - Server::Admin& admin, Router::RouteConfigProviderManager& route_config_provider_manager) + OptRef admin, + Router::RouteConfigProviderManager& route_config_provider_manager) : Envoy::Config::ConfigProviderManagerImplBase(admin, "route_scopes"), route_config_provider_manager_(route_config_provider_manager) {} diff --git a/source/common/secret/secret_manager_impl.cc b/source/common/secret/secret_manager_impl.cc index 9d1e8e06b56a2..ac7624e5d9016 100644 --- a/source/common/secret/secret_manager_impl.cc +++ b/source/common/secret/secret_manager_impl.cc @@ -17,11 +17,15 @@ namespace Envoy { namespace Secret { -SecretManagerImpl::SecretManagerImpl(Server::ConfigTracker& config_tracker) - : config_tracker_entry_( - config_tracker.add("secrets", [this](const Matchers::StringMatcher& name_matcher) { - return dumpSecretConfigs(name_matcher); - })) {} +SecretManagerImpl::SecretManagerImpl(OptRef config_tracker) { + if (config_tracker.has_value()) { + config_tracker_entry_ = + config_tracker->add("secrets", [this](const Matchers::StringMatcher& name_matcher) { + return dumpSecretConfigs(name_matcher); + }); + } +} + void SecretManagerImpl::addStaticSecret( const envoy::extensions::transport_sockets::tls::v3::Secret& secret) { switch (secret.type_case()) { diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index a3496b3c52b39..952206f106b73 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -18,7 +18,7 @@ namespace Secret { class SecretManagerImpl : public SecretManager { public: - SecretManagerImpl(Server::ConfigTracker& config_tracker); + SecretManagerImpl(OptRef config_tracker); void addStaticSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override; diff --git a/source/common/upstream/cluster_factory_impl.h b/source/common/upstream/cluster_factory_impl.h index 57f9499716ce7..5d38bff9b9741 100644 --- a/source/common/upstream/cluster_factory_impl.h +++ b/source/common/upstream/cluster_factory_impl.h @@ -71,7 +71,7 @@ class ClusterFactoryContextImpl : public ClusterFactoryContext { AccessLog::AccessLogManager& logManager() override { return server_context_.accessLogManager(); } const LocalInfo::LocalInfo& localInfo() const override { return server_context_.localInfo(); } const Server::Options& options() override { return server_context_.options(); } - Server::Admin& admin() override { return server_context_.admin(); } + OptRef admin() override { return server_context_.admin(); } Api::Api& api() override { return server_context_.api(); } Singleton::Manager& singletonManager() override { return server_context_.singletonManager(); } diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index c676fac636f3b..5b1f4c62d23db 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -283,7 +283,7 @@ ClusterManagerImpl::ClusterManagerImpl( const envoy::config::bootstrap::v3::Bootstrap& bootstrap, ClusterManagerFactory& factory, Stats::Store& stats, ThreadLocal::Instance& tls, Runtime::Loader& runtime, const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager, - Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, + Event::Dispatcher& main_thread_dispatcher, OptRef admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context, const Server::Instance& server) @@ -292,11 +292,6 @@ ClusterManagerImpl::ClusterManagerImpl( bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), cm_stats_(generateStats(stats)), init_helper_(*this, [this](ClusterManagerCluster& cluster) { onClusterInit(cluster); }), - config_tracker_entry_( - admin.getConfigTracker().add("clusters", - [this](const Matchers::StringMatcher& name_matcher) { - return dumpClusterConfigs(name_matcher); - })), time_source_(main_thread_dispatcher.timeSource()), dispatcher_(main_thread_dispatcher), http_context_(http_context), router_context_(router_context), cluster_stat_names_(stats.symbolTable()), @@ -304,6 +299,12 @@ ClusterManagerImpl::ClusterManagerImpl( cluster_circuit_breakers_stat_names_(stats.symbolTable()), cluster_request_response_size_stat_names_(stats.symbolTable()), cluster_timeout_budget_stat_names_(stats.symbolTable()) { + if (admin.has_value()) { + config_tracker_entry_ = admin->getConfigTracker().add( + "clusters", [this](const Matchers::StringMatcher& name_matcher) { + return dumpClusterConfigs(name_matcher); + }); + } async_client_manager_ = std::make_unique( *this, tls, time_source_, api, grpc_context.statNames()); const auto& cm_config = bootstrap.cluster_manager(); diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index a779bef35ad1a..13cfb41222c27 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -50,7 +50,7 @@ namespace Upstream { class ProdClusterManagerFactory : public ClusterManagerFactory { public: ProdClusterManagerFactory( - Server::Configuration::ServerFactoryContext& server_context, Server::Admin& admin, + Server::Configuration::ServerFactoryContext& server_context, OptRef admin, Runtime::Loader& runtime, Stats::Store& stats, ThreadLocal::Instance& tls, Network::DnsResolverSharedPtr dns_resolver, Ssl::ContextManager& ssl_context_manager, Event::Dispatcher& main_thread_dispatcher, const LocalInfo::LocalInfo& local_info, @@ -105,7 +105,7 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { Http::Context& http_context_; Grpc::Context& grpc_context_; Router::Context& router_context_; - Server::Admin& admin_; + OptRef admin_; Stats::Store& stats_; ThreadLocal::Instance& tls_; Network::DnsResolverSharedPtr dns_resolver_; @@ -245,7 +245,7 @@ class ClusterManagerImpl : public ClusterManager, ThreadLocal::Instance& tls, Runtime::Loader& runtime, const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager, - Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, + Event::Dispatcher& main_thread_dispatcher, OptRef admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context, const Server::Instance& server); diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index b6bc7cc4f8e31..2ec243d348a29 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -877,7 +877,7 @@ class FactoryContextImpl : public Server::Configuration::CommonFactoryContext { Stats::Scope& serverScope() override { return server_scope_; } Singleton::Manager& singletonManager() override { return singleton_manager_; } ThreadLocal::SlotAllocator& threadLocal() override { return tls_; } - Server::Admin& admin() override { return admin_; } + OptRef admin() override { return admin_; } TimeSource& timeSource() override { return api().timeSource(); } ProtobufMessage::ValidationContext& messageValidationContext() override { // TODO(davinci26): Needs an implementation for this context. Currently not used. @@ -906,7 +906,7 @@ class FactoryContextImpl : public Server::Configuration::CommonFactoryContext { Api::Api& api() override { return api_; } private: - Server::Admin& admin_; + OptRef admin_; Stats::Scope& server_scope_; Stats::Scope& stats_scope_; Upstream::ClusterManager& cluster_manager_; diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index 6d91d8c67a007..7bbeef5447307 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -186,12 +186,14 @@ bool MainCommonBase::run() { void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method, const AdminRequestFn& handler) { + RELEASE_ASSERT(server_->admin().has_value(), + "adminRequest called with admin support compiled out"); std::string path_and_query_buf = std::string(path_and_query); std::string method_buf = std::string(method); server_->dispatcher().post([this, path_and_query_buf, method_buf, handler]() { auto response_headers = Http::ResponseHeaderMapImpl::create(); std::string body; - server_->admin().request(path_and_query_buf, method_buf, *response_headers, body); + server_->admin()->request(path_and_query_buf, method_buf, *response_headers, body); handler(*response_headers, body); }); } diff --git a/source/extensions/common/tap/BUILD b/source/extensions/common/tap/BUILD index c70943346fb7c..452fa65c61611 100644 --- a/source/extensions/common/tap/BUILD +++ b/source/extensions/common/tap/BUILD @@ -2,6 +2,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_extension_package", + "envoy_select_admin_functionality", ) licenses(["notice"]) # Apache 2 @@ -35,8 +36,8 @@ envoy_cc_library( envoy_cc_library( name = "admin", - srcs = ["admin.cc"], - hdrs = ["admin.h"], + srcs = envoy_select_admin_functionality(["admin.cc"]), + hdrs = envoy_select_admin_functionality(["admin.h"]), deps = [ ":tap_interface", "//envoy/server:admin_interface", @@ -50,8 +51,8 @@ envoy_cc_library( envoy_cc_library( name = "extension_config_base", - srcs = ["extension_config_base.cc"], - hdrs = ["extension_config_base.h"], + srcs = envoy_select_admin_functionality(["extension_config_base.cc"]), + hdrs = envoy_select_admin_functionality(["extension_config_base.h"]), deps = [ "//envoy/thread_local:thread_local_interface", "//source/extensions/common/tap:admin", diff --git a/source/extensions/common/tap/admin.cc b/source/extensions/common/tap/admin.cc index 8fed89e7660de..122277eaac0e0 100644 --- a/source/extensions/common/tap/admin.cc +++ b/source/extensions/common/tap/admin.cc @@ -17,7 +17,7 @@ namespace Tap { // Singleton registration via macro defined in envoy/singleton/manager.h SINGLETON_MANAGER_REGISTRATION(tap_admin_handler); -AdminHandlerSharedPtr AdminHandler::getSingleton(Server::Admin& admin, +AdminHandlerSharedPtr AdminHandler::getSingleton(OptRef admin, Singleton::Manager& singleton_manager, Event::Dispatcher& main_thread_dispatcher) { return singleton_manager.getTyped( @@ -26,8 +26,8 @@ AdminHandlerSharedPtr AdminHandler::getSingleton(Server::Admin& admin, }); } -AdminHandler::AdminHandler(Server::Admin& admin, Event::Dispatcher& main_thread_dispatcher) - : admin_(admin), main_thread_dispatcher_(main_thread_dispatcher) { +AdminHandler::AdminHandler(OptRef admin, Event::Dispatcher& main_thread_dispatcher) + : admin_(admin.value()), main_thread_dispatcher_(main_thread_dispatcher) { const bool rc = admin_.addHandler("/tap", "tap filter control", MAKE_ADMIN_HANDLER(handler), true, true); RELEASE_ASSERT(rc, "/tap admin endpoint is taken"); diff --git a/source/extensions/common/tap/admin.h b/source/extensions/common/tap/admin.h index 56ffffcde436d..126ff2af63de2 100644 --- a/source/extensions/common/tap/admin.h +++ b/source/extensions/common/tap/admin.h @@ -36,14 +36,14 @@ class AdminHandler : public Singleton::Instance, public Extensions::Common::Tap::Sink, Logger::Loggable { public: - AdminHandler(Server::Admin& admin, Event::Dispatcher& main_thread_dispatcher); + AdminHandler(OptRef admin, Event::Dispatcher& main_thread_dispatcher); ~AdminHandler() override; /** * Get the singleton admin handler. The handler will be created if it doesn't already exist, * otherwise the existing handler will be returned. */ - static AdminHandlerSharedPtr getSingleton(Server::Admin& admin, + static AdminHandlerSharedPtr getSingleton(OptRef admin, Singleton::Manager& singleton_manager, Event::Dispatcher& main_thread_dispatcher); diff --git a/source/extensions/common/tap/extension_config_base.cc b/source/extensions/common/tap/extension_config_base.cc index afbee4d4687a9..8d15d98bbb4ce 100644 --- a/source/extensions/common/tap/extension_config_base.cc +++ b/source/extensions/common/tap/extension_config_base.cc @@ -10,7 +10,7 @@ namespace Tap { ExtensionConfigBase::ExtensionConfigBase( const envoy::extensions::common::tap::v3::CommonExtensionConfig proto_config, - TapConfigFactoryPtr&& config_factory, Server::Admin& admin, + TapConfigFactoryPtr&& config_factory, OptRef admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher) : proto_config_(proto_config), config_factory_(std::move(config_factory)), tls_slot_(tls) { diff --git a/source/extensions/common/tap/extension_config_base.h b/source/extensions/common/tap/extension_config_base.h index ff8f87646a6c8..11b65bde4c4b7 100644 --- a/source/extensions/common/tap/extension_config_base.h +++ b/source/extensions/common/tap/extension_config_base.h @@ -25,7 +25,7 @@ class ExtensionConfigBase : public ExtensionConfig, Logger::Loggable admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher); ~ExtensionConfigBase() override; diff --git a/source/extensions/filters/http/tap/BUILD b/source/extensions/filters/http/tap/BUILD index 526f6d250e0c0..77913c8ae7698 100644 --- a/source/extensions/filters/http/tap/BUILD +++ b/source/extensions/filters/http/tap/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_extension", "envoy_cc_library", "envoy_extension_package", + "envoy_select_admin_functionality", ) licenses(["notice"]) # Apache 2 @@ -37,8 +38,8 @@ envoy_cc_library( envoy_cc_library( name = "tap_filter_lib", - srcs = ["tap_filter.cc"], - hdrs = ["tap_filter.h"], + srcs = envoy_select_admin_functionality(["tap_filter.cc"]), + hdrs = envoy_select_admin_functionality(["tap_filter.h"]), deps = [ ":tap_config_interface", "//envoy/access_log:access_log_interface", @@ -50,8 +51,8 @@ envoy_cc_library( envoy_cc_extension( name = "config", - srcs = ["config.cc"], - hdrs = ["config.h"], + srcs = envoy_select_admin_functionality(["config.cc"]), + hdrs = envoy_select_admin_functionality(["config.h"]), deps = [ ":tap_config_impl", ":tap_filter_lib", diff --git a/source/extensions/filters/http/tap/tap_filter.cc b/source/extensions/filters/http/tap/tap_filter.cc index d64b3407fde3a..63c50d01a246c 100644 --- a/source/extensions/filters/http/tap/tap_filter.cc +++ b/source/extensions/filters/http/tap/tap_filter.cc @@ -10,7 +10,7 @@ namespace TapFilter { FilterConfigImpl::FilterConfigImpl( const envoy::extensions::filters::http::tap::v3::Tap& proto_config, const std::string& stats_prefix, Common::Tap::TapConfigFactoryPtr&& config_factory, - Stats::Scope& scope, Server::Admin& admin, Singleton::Manager& singleton_manager, + Stats::Scope& scope, OptRef admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher) : ExtensionConfigBase(proto_config.common_config(), std::move(config_factory), admin, singleton_manager, tls, main_thread_dispatcher), diff --git a/source/extensions/filters/http/tap/tap_filter.h b/source/extensions/filters/http/tap/tap_filter.h index 1414b077cd6ec..0e47bb1433970 100644 --- a/source/extensions/filters/http/tap/tap_filter.h +++ b/source/extensions/filters/http/tap/tap_filter.h @@ -59,8 +59,9 @@ class FilterConfigImpl : public FilterConfig, public Extensions::Common::Tap::Ex FilterConfigImpl(const envoy::extensions::filters::http::tap::v3::Tap& proto_config, const std::string& stats_prefix, Extensions::Common::Tap::TapConfigFactoryPtr&& config_factory, - Stats::Scope& scope, Server::Admin& admin, Singleton::Manager& singleton_manager, - ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher); + Stats::Scope& scope, OptRef admin, + Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, + Event::Dispatcher& main_thread_dispatcher); // FilterConfig HttpTapConfigSharedPtr currentConfig() override; diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index 2ae45959cdf37..dc1609bff4027 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -283,11 +283,12 @@ HystrixSink::HystrixSink(Server::Configuration::ServerFactoryContext& server, upstream_rq_2xx_(stat_name_pool_.add("upstream_rq_2xx")), upstream_rq_4xx_(stat_name_pool_.add("upstream_rq_4xx")), upstream_rq_5xx_(stat_name_pool_.add("upstream_rq_5xx")) { - Server::Admin& admin = server_.admin(); - ENVOY_LOG(debug, - "adding hystrix_event_stream endpoint to enable connection to hystrix dashboard"); - admin.addHandler("/hystrix_event_stream", "send hystrix event stream", - MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); + if (server.admin().has_value()) { + ENVOY_LOG(debug, + "adding hystrix_event_stream endpoint to enable connection to hystrix dashboard"); + server.admin()->addHandler("/hystrix_event_stream", "send hystrix event stream", + MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); + } } Http::Code HystrixSink::handlerHystrixEventStream(Http::ResponseHeaderMap& response_headers, diff --git a/source/extensions/transport_sockets/tap/BUILD b/source/extensions/transport_sockets/tap/BUILD index 03bd2a581454a..043f881df364c 100644 --- a/source/extensions/transport_sockets/tap/BUILD +++ b/source/extensions/transport_sockets/tap/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_extension", "envoy_cc_library", "envoy_extension_package", + "envoy_select_admin_functionality", ) licenses(["notice"]) # Apache 2 @@ -35,8 +36,8 @@ envoy_cc_library( envoy_cc_library( name = "tap_lib", - srcs = ["tap.cc"], - hdrs = ["tap.h"], + srcs = envoy_select_admin_functionality(["tap.cc"]), + hdrs = envoy_select_admin_functionality(["tap.h"]), deps = [ ":tap_config_interface", "//envoy/network:transport_socket_interface", @@ -49,8 +50,8 @@ envoy_cc_library( envoy_cc_extension( name = "config", - srcs = ["config.cc"], - hdrs = ["config.h"], + srcs = envoy_select_admin_functionality(["config.cc"]), + hdrs = envoy_select_admin_functionality(["config.h"]), deps = [ ":tap_config_impl", ":tap_lib", diff --git a/source/extensions/transport_sockets/tap/tap.cc b/source/extensions/transport_sockets/tap/tap.cc index 218d4648b69da..49d9e0f3d8a71 100644 --- a/source/extensions/transport_sockets/tap/tap.cc +++ b/source/extensions/transport_sockets/tap/tap.cc @@ -48,7 +48,7 @@ Network::IoResult TapSocket::doWrite(Buffer::Instance& buffer, bool end_stream) TapSocketFactory::TapSocketFactory( const envoy::extensions::transport_sockets::tap::v3::Tap& proto_config, - Common::Tap::TapConfigFactoryPtr&& config_factory, Server::Admin& admin, + Common::Tap::TapConfigFactoryPtr&& config_factory, OptRef admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher, Network::UpstreamTransportSocketFactoryPtr&& transport_socket_factory) @@ -66,7 +66,7 @@ TapSocketFactory::createTransportSocket(Network::TransportSocketOptionsConstShar DownstreamTapSocketFactory::DownstreamTapSocketFactory( const envoy::extensions::transport_sockets::tap::v3::Tap& proto_config, - Common::Tap::TapConfigFactoryPtr&& config_factory, Server::Admin& admin, + Common::Tap::TapConfigFactoryPtr&& config_factory, OptRef admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher, Network::DownstreamTransportSocketFactoryPtr&& transport_socket_factory) diff --git a/source/extensions/transport_sockets/tap/tap.h b/source/extensions/transport_sockets/tap/tap.h index d944c7f01bd72..db51961b70ce4 100644 --- a/source/extensions/transport_sockets/tap/tap.h +++ b/source/extensions/transport_sockets/tap/tap.h @@ -31,7 +31,7 @@ class TapSocket : public TransportSockets::PassthroughSocket { class TapSocketFactory : public Common::Tap::ExtensionConfigBase, public PassthroughFactory { public: TapSocketFactory(const envoy::extensions::transport_sockets::tap::v3::Tap& proto_config, - Common::Tap::TapConfigFactoryPtr&& config_factory, Server::Admin& admin, + Common::Tap::TapConfigFactoryPtr&& config_factory, OptRef admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher, Network::UpstreamTransportSocketFactoryPtr&& transport_socket_factory); @@ -47,7 +47,7 @@ class DownstreamTapSocketFactory : public Common::Tap::ExtensionConfigBase, public: DownstreamTapSocketFactory( const envoy::extensions::transport_sockets::tap::v3::Tap& proto_config, - Common::Tap::TapConfigFactoryPtr&& config_factory, Server::Admin& admin, + Common::Tap::TapConfigFactoryPtr&& config_factory, OptRef admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher, Network::DownstreamTransportSocketFactoryPtr&& transport_socket_factory); diff --git a/source/server/admin/BUILD b/source/server/admin/BUILD index c9e988f6b8a0b..0c526edc73ded 100644 --- a/source/server/admin/BUILD +++ b/source/server/admin/BUILD @@ -2,16 +2,25 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", + "envoy_select_admin_functionality", "envoy_select_admin_html", "envoy_select_admin_no_html", ) licenses(["notice"]) # Apache 2 -envoy_package() +# Admin libraries are private (tests exempted) by default. +# Any admin libraries with public visibility must have envoy_select_admin_functionality rules. +envoy_package(["//test:__subpackages__"]) envoy_cc_library( name = "admin_lib", + visibility = ["//visibility:public"], + deps = [] + envoy_select_admin_functionality(["//source/server/admin:admin_lib_internal"]), +) + +envoy_cc_library( + name = "admin_lib_internal", srcs = ["admin.cc"] + envoy_select_admin_html([ "admin_html.cc", ]) + envoy_select_admin_no_html([ @@ -98,7 +107,6 @@ genrule( cmd = "./$(location :generate_admin_html.sh) \ $(location admin_head_start.html) $(location admin.css) > $@", tools = [":generate_admin_html.sh"], - visibility = ["//visibility:private"], ) envoy_cc_library( @@ -367,6 +375,7 @@ envoy_cc_library( name = "config_tracker_lib", srcs = ["config_tracker_impl.cc"], hdrs = ["config_tracker_impl.h"], + visibility = ["//visibility:public"], deps = [ "//envoy/server:config_tracker_interface", "//source/common/common:assert_lib", diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index ce561f1aae677..780702bdc2a6a 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -199,7 +199,7 @@ class AdminImpl : public Admin, } Http::Code request(absl::string_view path_and_query, absl::string_view method, Http::ResponseHeaderMap& response_headers, std::string& body) override; - void closeSocket(); + void closeSocket() override; void addListenerToHandler(Network::ConnectionHandler* handler) override; GenRequestFn createRequestFunction() const { diff --git a/source/server/config_validation/BUILD b/source/server/config_validation/BUILD index bc6fd2391fcd5..52326bdd8cb5a 100644 --- a/source/server/config_validation/BUILD +++ b/source/server/config_validation/BUILD @@ -86,6 +86,7 @@ envoy_cc_library( "//envoy/tracing:http_tracer_interface", "//source/common/access_log:access_log_manager_lib", "//source/common/common:assert_lib", + "//source/common/common:random_generator_lib", "//source/common/common:utility_lib", "//source/common/config:utility_lib", "//source/common/grpc:common_lib", diff --git a/source/server/config_validation/admin.h b/source/server/config_validation/admin.h index ca9a0a44a8ae5..150a8dbfac079 100644 --- a/source/server/config_validation/admin.h +++ b/source/server/config_validation/admin.h @@ -39,6 +39,7 @@ class ValidationAdmin : public Admin { Http::ResponseHeaderMap& response_headers, std::string& body) override; void addListenerToHandler(Network::ConnectionHandler* handler) override; uint32_t concurrency() const override { return 1; } + void closeSocket() override {} private: ConfigTrackerImpl config_tracker_; diff --git a/source/server/config_validation/cluster_manager.h b/source/server/config_validation/cluster_manager.h index c66d273542f2b..ffb7917050124 100644 --- a/source/server/config_validation/cluster_manager.h +++ b/source/server/config_validation/cluster_manager.h @@ -21,7 +21,7 @@ class ValidationClusterManagerFactory : public ProdClusterManagerFactory { using ProdClusterManagerFactory::ProdClusterManagerFactory; explicit ValidationClusterManagerFactory( - Server::Admin& admin, Runtime::Loader& runtime, Stats::Store& stats, + OptRef admin, Runtime::Loader& runtime, Stats::Store& stats, ThreadLocal::Instance& tls, Network::DnsResolverSharedPtr dns_resolver, Ssl::ContextManager& ssl_context_manager, Event::Dispatcher& main_thread_dispatcher, const LocalInfo::LocalInfo& local_info, Secret::SecretManager& secret_manager, diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 94f0413999b6c..31f576d12546d 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -108,7 +108,7 @@ void ValidationInstance::initialize(const Options& options, runtime_singleton_ = std::make_unique(std::move(runtime_ptr)); } - secret_manager_ = std::make_unique(admin().getConfigTracker()); + secret_manager_ = std::make_unique(admin()->getConfigTracker()); ssl_context_manager_ = createContextManager("ssl_context_manager", api_->timeSource()); cluster_manager_factory_ = std::make_unique( admin(), runtime(), stats(), threadLocal(), dnsResolver(), sslContextManager(), dispatcher(), diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index 73cff10adf82a..f23501dc60e17 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -24,7 +24,6 @@ #include "source/common/runtime/runtime_impl.h" #include "source/common/secret/secret_manager_impl.h" #include "source/common/thread_local/thread_local_impl.h" -#include "source/server/admin/admin.h" #include "source/server/config_validation/admin.h" #include "source/server/config_validation/api.h" #include "source/server/config_validation/cluster_manager.h" @@ -71,7 +70,9 @@ class ValidationInstance final : Logger::Loggable, Filesystem::Instance& file_system); // Server::Instance - Admin& admin() override { return *admin_; } + OptRef admin() override { + return makeOptRefFromPtr(reinterpret_cast(admin_.get())); + } Api::Api& api() override { return *api_; } Upstream::ClusterManager& clusterManager() override { return *config_.clusterManager(); } const Upstream::ClusterManager& clusterManager() const override { diff --git a/source/server/factory_context_base_impl.h b/source/server/factory_context_base_impl.h index 0579f86a4eb51..a7aea9c37c539 100644 --- a/source/server/factory_context_base_impl.h +++ b/source/server/factory_context_base_impl.h @@ -9,7 +9,7 @@ class FactoryContextBaseImpl : public Configuration::FactoryContextBase { public: FactoryContextBaseImpl(const Server::Options& options, Event::Dispatcher& main_thread_dispatcher, Api::Api& api, const LocalInfo::LocalInfo& local_info, - Server::Admin& admin, Runtime::Loader& runtime, + OptRef admin, Runtime::Loader& runtime, Singleton::Manager& singleton_manager, ProtobufMessage::ValidationVisitor& validation_visitor, Stats::Store& scope, ThreadLocal::Instance& local) @@ -30,7 +30,7 @@ class FactoryContextBaseImpl : public Configuration::FactoryContextBase { Event::Dispatcher& mainThreadDispatcher() override { return main_thread_dispatcher_; }; Api::Api& api() override { return api_; }; const LocalInfo::LocalInfo& localInfo() const override { return local_info_; }; - Server::Admin& admin() override { return admin_; }; + OptRef admin() override { return admin_; }; Envoy::Runtime::Loader& runtime() override { return runtime_; }; Singleton::Manager& singletonManager() override { return singleton_manager_; }; ProtobufMessage::ValidationVisitor& messageValidationVisitor() override { @@ -45,7 +45,7 @@ class FactoryContextBaseImpl : public Configuration::FactoryContextBase { Event::Dispatcher& main_thread_dispatcher_; Api::Api& api_; const LocalInfo::LocalInfo& local_info_; - Server::Admin& admin_; + OptRef admin_; Runtime::Loader& runtime_; Singleton::Manager& singleton_manager_; ProtobufMessage::ValidationVisitor& validation_visitor_; diff --git a/source/server/filter_chain_manager_impl.cc b/source/server/filter_chain_manager_impl.cc index 6dde01909da41..206197723de45 100644 --- a/source/server/filter_chain_manager_impl.cc +++ b/source/server/filter_chain_manager_impl.cc @@ -164,7 +164,7 @@ OverloadManager& PerFilterChainFactoryContextImpl::overloadManager() { return parent_context_.overloadManager(); } -Admin& PerFilterChainFactoryContextImpl::admin() { return parent_context_.admin(); } +OptRef PerFilterChainFactoryContextImpl::admin() { return parent_context_.admin(); } TimeSource& PerFilterChainFactoryContextImpl::timeSource() { return api().timeSource(); } @@ -870,7 +870,7 @@ Stats::Scope& FactoryContextImpl::scope() { return global_scope_; } Singleton::Manager& FactoryContextImpl::singletonManager() { return server_.singletonManager(); } OverloadManager& FactoryContextImpl::overloadManager() { return server_.overloadManager(); } ThreadLocal::SlotAllocator& FactoryContextImpl::threadLocal() { return server_.threadLocal(); } -Admin& FactoryContextImpl::admin() { return server_.admin(); } +OptRef FactoryContextImpl::admin() { return server_.admin(); } TimeSource& FactoryContextImpl::timeSource() { return server_.timeSource(); } ProtobufMessage::ValidationContext& FactoryContextImpl::messageValidationContext() { return server_.messageValidationContext(); diff --git a/source/server/filter_chain_manager_impl.h b/source/server/filter_chain_manager_impl.h index c9503eafd8e9b..0d6a0f79b9e33 100644 --- a/source/server/filter_chain_manager_impl.h +++ b/source/server/filter_chain_manager_impl.h @@ -73,7 +73,7 @@ class PerFilterChainFactoryContextImpl : public Configuration::FilterChainFactor Singleton::Manager& singletonManager() override; OverloadManager& overloadManager() override; ThreadLocal::SlotAllocator& threadLocal() override; - Admin& admin() override; + OptRef admin() override; const envoy::config::core::v3::Metadata& listenerMetadata() const override; const Envoy::Config::TypedMetadata& listenerTypedMetadata() const override; envoy::config::core::v3::TrafficDirection direction() const override; @@ -164,7 +164,7 @@ class FactoryContextImpl : public Configuration::FactoryContext { Singleton::Manager& singletonManager() override; OverloadManager& overloadManager() override; ThreadLocal::SlotAllocator& threadLocal() override; - Admin& admin() override; + OptRef admin() override; TimeSource& timeSource() override; ProtobufMessage::ValidationContext& messageValidationContext() override; ProtobufMessage::ValidationVisitor& messageValidationVisitor() override; diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc index d4d28b0a5a838..817cf40eb8ac4 100644 --- a/source/server/listener_impl.cc +++ b/source/server/listener_impl.cc @@ -263,7 +263,7 @@ OverloadManager& ListenerFactoryContextBaseImpl::overloadManager() { ThreadLocal::Instance& ListenerFactoryContextBaseImpl::threadLocal() { return server_.threadLocal(); } -Admin& ListenerFactoryContextBaseImpl::admin() { return server_.admin(); } +OptRef ListenerFactoryContextBaseImpl::admin() { return server_.admin(); } const envoy::config::core::v3::Metadata& ListenerFactoryContextBaseImpl::listenerMetadata() const { return metadata_; }; @@ -861,7 +861,9 @@ OverloadManager& PerListenerFactoryContextImpl::overloadManager() { ThreadLocal::Instance& PerListenerFactoryContextImpl::threadLocal() { return listener_factory_context_base_->threadLocal(); } -Admin& PerListenerFactoryContextImpl::admin() { return listener_factory_context_base_->admin(); } +OptRef PerListenerFactoryContextImpl::admin() { + return listener_factory_context_base_->admin(); +} const envoy::config::core::v3::Metadata& PerListenerFactoryContextImpl::listenerMetadata() const { return listener_factory_context_base_->listenerMetadata(); }; diff --git a/source/server/listener_impl.h b/source/server/listener_impl.h index 7ed29495567e6..d910acc2c2502 100644 --- a/source/server/listener_impl.h +++ b/source/server/listener_impl.h @@ -138,7 +138,7 @@ class ListenerFactoryContextBaseImpl final : public Configuration::FactoryContex Singleton::Manager& singletonManager() override; OverloadManager& overloadManager() override; ThreadLocal::Instance& threadLocal() override; - Admin& admin() override; + OptRef admin() override; const envoy::config::core::v3::Metadata& listenerMetadata() const override; const Envoy::Config::TypedMetadata& listenerTypedMetadata() const override; envoy::config::core::v3::TrafficDirection direction() const override; @@ -215,7 +215,7 @@ class PerListenerFactoryContextImpl : public Configuration::ListenerFactoryConte Singleton::Manager& singletonManager() override; OverloadManager& overloadManager() override; ThreadLocal::Instance& threadLocal() override; - Admin& admin() override; + OptRef admin() override; const envoy::config::core::v3::Metadata& listenerMetadata() const override; const Envoy::Config::TypedMetadata& listenerTypedMetadata() const override; envoy::config::core::v3::TrafficDirection direction() const override; diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 9fa6bfb74a205..fcf1fd61acc79 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -283,12 +283,14 @@ ListenerManagerImpl::ListenerManagerImpl(Instance& server, Quic::QuicStatNames& quic_stat_names) : server_(server), factory_(listener_factory), scope_(server.stats().createScope("listener_manager.")), stats_(generateStats(*scope_)), - config_tracker_entry_(server.admin().getConfigTracker().add( - "listeners", - [this](const Matchers::StringMatcher& name_matcher) { - return dumpListenerConfigs(name_matcher); - })), enable_dispatcher_stats_(enable_dispatcher_stats), quic_stat_names_(quic_stat_names) { + if (server.admin().has_value()) { + config_tracker_entry_ = server.admin()->getConfigTracker().add( + "listeners", [this](const Matchers::StringMatcher& name_matcher) { + return dumpListenerConfigs(name_matcher); + }); + } + for (uint32_t i = 0; i < server.options().concurrency(); i++) { workers_.emplace_back( worker_factory.createWorker(i, server.overloadManager(), absl::StrCat("worker_", i))); diff --git a/source/server/server.cc b/source/server/server.cc index 1e1a427c95c83..c594cf6b43a63 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -52,7 +52,6 @@ #include "source/common/stats/timespan_impl.h" #include "source/common/upstream/cluster_manager_impl.h" #include "source/common/version/version.h" -#include "source/server/admin/utils.h" #include "source/server/configuration_impl.h" #include "source/server/connection_handler_impl.h" #include "source/server/guarddog_impl.h" @@ -61,6 +60,23 @@ namespace Envoy { namespace Server { +namespace { +// TODO(alyssawilk) resolve the copy/paste code here. +envoy::admin::v3::ServerInfo::State serverState(Init::Manager::State state, + bool health_check_failed) { + switch (state) { + case Init::Manager::State::Uninitialized: + return envoy::admin::v3::ServerInfo::PRE_INITIALIZING; + case Init::Manager::State::Initializing: + return envoy::admin::v3::ServerInfo::INITIALIZING; + case Init::Manager::State::Initialized: + return health_check_failed ? envoy::admin::v3::ServerInfo::DRAINING + : envoy::admin::v3::ServerInfo::LIVE; + } + IS_ENVOY_BUG("unexpected server state enum"); + return envoy::admin::v3::ServerInfo::PRE_INITIALIZING; +} +} // namespace InstanceImpl::InstanceImpl( Init::Manager& init_manager, const Options& options, Event::TimeSystem& time_system, @@ -284,8 +300,7 @@ void InstanceImpl::updateServerStats() { 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(serverState(initManager().state(), healthCheckFailed()))); server_stats_->stats_recent_lookups_.set( stats_store_.symbolTable().getRecentLookups([](absl::string_view, uint64_t) {})); } @@ -558,12 +573,17 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add enable_reuse_port_default_ = parent_admin_shutdown_response.value().enable_reuse_port_default_ ? true : false; } + + OptRef config_tracker; +#ifdef ENVOY_ADMIN_FUNCTIONALITY admin_ = std::make_unique(initial_config.admin().profilePath(), *this, initial_config.admin().ignoreGlobalConnLimit()); - loadServerFlags(initial_config.flagsPath()); + config_tracker = admin_->getConfigTracker(); +#endif + secret_manager_ = std::make_unique(config_tracker); - secret_manager_ = std::make_unique(admin_->getConfigTracker()); + loadServerFlags(initial_config.flagsPath()); // Initialize the overload manager early so other modules can register for actions. overload_manager_ = std::make_unique( @@ -663,6 +683,9 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add } if (initial_config.admin().address()) { + if (!admin_) { + throw EnvoyException("Admin address configured but admin support compiled out"); + } admin_->startHttpListener(initial_config.admin().accessLogs(), options_.adminAddressPath(), initial_config.admin().address(), initial_config.admin().socketOptions(), @@ -670,8 +693,10 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add } else { ENVOY_LOG(warn, "No admin address given, so no admin HTTP server started."); } - config_tracker_entry_ = admin_->getConfigTracker().add( - "bootstrap", [this](const Matchers::StringMatcher&) { return dumpBootstrapConfig(); }); + if (admin_) { + config_tracker_entry_ = admin_->getConfigTracker().add( + "bootstrap", [this](const Matchers::StringMatcher&) { return dumpBootstrapConfig(); }); + } if (initial_config.admin().address()) { admin_->addListenerToHandler(handler_.get()); } @@ -686,7 +711,7 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add dns_resolver_factory.createDnsResolver(dispatcher(), api(), typed_dns_resolver_config); cluster_manager_factory_ = std::make_unique( - serverFactoryContext(), *admin_, runtime(), stats_store_, thread_local_, dns_resolver_, + serverFactoryContext(), admin(), runtime(), stats_store_, thread_local_, dns_resolver_, *ssl_context_manager_, *dispatcher_, *local_info_, *secret_manager_, messageValidationContext(), *api_, http_context_, grpc_context_, router_context_, access_log_manager_, *singleton_manager_, options_, quic_stat_names_, *this); @@ -984,7 +1009,9 @@ void InstanceImpl::shutdownAdmin() { ENVOY_LOG(warn, "shutting down admin due to child startup"); stat_flush_timer_.reset(); handler_->stopListeners(); - admin_->closeSocket(); + if (admin_) { + admin_->closeSocket(); + } // If we still have a parent, it should be terminated now that we have a child. ENVOY_LOG(warn, "terminating parent process"); diff --git a/source/server/server.h b/source/server/server.h index 1be756114d567..39ec228653ec5 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -39,7 +39,10 @@ #include "source/common/runtime/runtime_impl.h" #include "source/common/secret/secret_manager_impl.h" #include "source/common/upstream/health_discovery_service.h" + +#ifdef ENVOY_ADMIN_FUNCTIONALITY #include "source/server/admin/admin.h" +#endif #include "source/server/configuration_impl.h" #include "source/server/listener_hooks.h" #include "source/server/listener_manager_impl.h" @@ -187,7 +190,7 @@ class ServerFactoryContextImpl : public Configuration::ServerFactoryContext, Stats::Scope& serverScope() override { return *server_scope_; } Singleton::Manager& singletonManager() override { return server_.singletonManager(); } ThreadLocal::Instance& threadLocal() override { return server_.threadLocal(); } - Admin& admin() override { return server_.admin(); } + OptRef admin() override { return server_.admin(); } TimeSource& timeSource() override { return api().timeSource(); } Api::Api& api() override { return server_.api(); } Grpc::Context& grpcContext() override { return server_.grpcContext(); } @@ -243,7 +246,7 @@ class InstanceImpl final : Logger::Loggable, void run(); // Server::Instance - Admin& admin() override { return *admin_; } + OptRef admin() override { return makeOptRefFromPtr(admin_.get()); } Api::Api& api() override { return *api_; } Upstream::ClusterManager& clusterManager() override; const Upstream::ClusterManager& clusterManager() const override; @@ -358,7 +361,7 @@ class InstanceImpl final : Logger::Loggable, std::unique_ptr ssl_context_manager_; Event::DispatcherPtr dispatcher_; AccessLog::AccessLogManagerImpl access_log_manager_; - std::unique_ptr admin_; + std::unique_ptr admin_; Singleton::ManagerPtr singleton_manager_; Network::ConnectionHandlerPtr handler_; std::unique_ptr runtime_singleton_; diff --git a/source/server/transport_socket_config_impl.h b/source/server/transport_socket_config_impl.h index 390b23f8ebe7e..190306b64dc8a 100644 --- a/source/server/transport_socket_config_impl.h +++ b/source/server/transport_socket_config_impl.h @@ -27,7 +27,7 @@ class TransportSocketFactoryContextImpl : public TransportSocketFactoryContext { void setInitManager(Init::Manager& init_manager) { init_manager_ = &init_manager; } // TransportSocketFactoryContext - Server::Admin& admin() override { return server_context_.admin(); } + OptRef admin() override { return server_context_.admin(); } Ssl::ContextManager& sslContextManager() override { return context_manager_; } Stats::Scope& scope() override { return stats_scope_; } Secret::SecretManager& secretManager() override { diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index a92502f318d39..bd58264f20e55 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -11,9 +11,12 @@ #include "source/common/config/utility.h" #include "source/common/json/json_loader.h" +#include "source/common/router/config_impl.h" #include "source/common/router/rds_impl.h" -#include "source/server/admin/admin.h" +#ifdef ENVOY_ADMIN_FUNCTIONALITY +#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" diff --git a/test/common/router/vhds_test.cc b/test/common/router/vhds_test.cc index 69d28aaa0d9d0..828d608ff4ffa 100644 --- a/test/common/router/vhds_test.cc +++ b/test/common/router/vhds_test.cc @@ -11,8 +11,10 @@ #include "source/common/protobuf/protobuf.h" #include "source/common/router/rds_impl.h" #include "source/common/router/route_config_update_receiver_impl.h" -#include "source/server/admin/admin.h" +#ifdef ENVOY_ADMIN_FUNCTIONALITY +#include "source/server/admin/admin.h" +#endif #include "test/mocks/config/mocks.h" #include "test/mocks/init/mocks.h" #include "test/mocks/server/instance.h" diff --git a/test/exe/BUILD b/test/exe/BUILD index b795c808f325e..f6d6e71c61f41 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -2,6 +2,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_package", + "envoy_select_admin_functionality", "envoy_sh_test", ) @@ -61,7 +62,7 @@ envoy_sh_test( envoy_cc_test( name = "main_common_test", - srcs = ["main_common_test.cc"], + srcs = envoy_select_admin_functionality(["main_common_test.cc"]), data = [ "//test/config/integration:google_com_proxy_port_0", ], diff --git a/test/extensions/common/tap/BUILD b/test/extensions/common/tap/BUILD index 5a5948d3eee0a..f65ad733eaf55 100644 --- a/test/extensions/common/tap/BUILD +++ b/test/extensions/common/tap/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_select_admin_functionality", ) licenses(["notice"]) # Apache 2 @@ -22,7 +23,7 @@ envoy_cc_test_library( envoy_cc_test( name = "admin_test", - srcs = ["admin_test.cc"], + srcs = envoy_select_admin_functionality(["admin_test.cc"]), deps = [ "//source/extensions/common/tap:admin", "//test/mocks/server:admin_mocks", diff --git a/test/extensions/filters/http/common/fuzz/uber_per_filter.cc b/test/extensions/filters/http/common/fuzz/uber_per_filter.cc index 61ab20c1df7a2..582fdd69938c7 100644 --- a/test/extensions/filters/http/common/fuzz/uber_per_filter.cc +++ b/test/extensions/filters/http/common/fuzz/uber_per_filter.cc @@ -162,7 +162,8 @@ void UberFilterFuzzer::perFilterSetup() { .WillByDefault(testing::Return(resolver_)); // Prepare expectations for TAP config. - ON_CALL(factory_context_, admin()).WillByDefault(testing::ReturnRef(factory_context_.admin_)); + ON_CALL(factory_context_, admin()) + .WillByDefault(testing::Return(OptRef{factory_context_.admin_})); ON_CALL(factory_context_.admin_, addHandler(_, _, _, _, _, _)) .WillByDefault(testing::Return(true)); ON_CALL(factory_context_.admin_, removeHandler(_)).WillByDefault(testing::Return(true)); diff --git a/test/extensions/filters/http/health_check/health_check_integration_test.cc b/test/extensions/filters/http/health_check/health_check_integration_test.cc index 8c0f18d21231a..93434658d3374 100644 --- a/test/extensions/filters/http/health_check/health_check_integration_test.cc +++ b/test/extensions/filters/http/health_check/health_check_integration_test.cc @@ -130,6 +130,7 @@ name: health_check } TEST_P(HealthCheckIntegrationTest, HealthCheck) { + DISABLE_IF_ADMIN_DISABLED; initialize(); BufferingStreamDecoderPtr response; @@ -143,6 +144,7 @@ TEST_P(HealthCheckIntegrationTest, HealthCheck) { } TEST_P(HealthCheckIntegrationTest, HealthCheckWithoutServerStats) { + DISABLE_IF_ADMIN_DISABLED; envoy::config::metrics::v3::StatsMatcher stats_matcher; stats_matcher.mutable_exclusion_list()->add_patterns()->set_prefix("server."); config_helper_.addConfigModifier( diff --git a/test/extensions/filters/http/tap/BUILD b/test/extensions/filters/http/tap/BUILD index b6efed8eb34eb..87172a032068a 100644 --- a/test/extensions/filters/http/tap/BUILD +++ b/test/extensions/filters/http/tap/BUILD @@ -2,6 +2,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_test_library", "envoy_package", + "envoy_select_admin_functionality", ) load( "//test/extensions:extensions_build_system.bzl", @@ -22,7 +23,7 @@ envoy_cc_test_library( envoy_extension_cc_test( name = "tap_filter_test", - srcs = ["tap_filter_test.cc"], + srcs = envoy_select_admin_functionality(["tap_filter_test.cc"]), extension_names = ["envoy.filters.http.tap"], deps = [ ":common", @@ -37,7 +38,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "tap_config_impl_test", - srcs = ["tap_config_impl_test.cc"], + srcs = envoy_select_admin_functionality(["tap_config_impl_test.cc"]), extension_names = ["envoy.filters.http.tap"], deps = [ ":common", @@ -50,7 +51,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "tap_filter_integration_test", - srcs = ["tap_filter_integration_test.cc"], + srcs = envoy_select_admin_functionality(["tap_filter_integration_test.cc"]), extension_names = ["envoy.filters.http.tap"], deps = [ "//source/extensions/filters/http/tap:config", diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc index 73605ece1e3e2..adf7513e1ded7 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc @@ -6,6 +6,7 @@ #include "envoy/extensions/access_loggers/file/v3/file.pb.h" #include "source/common/config/api_version.h" +#include "source/common/network/raw_buffer_socket.h" #include "source/common/network/utility.h" #include "source/extensions/filters/listener/tls_inspector/tls_inspector.h" #include "source/extensions/transport_sockets/tls/context_manager_impl.h" diff --git a/test/extensions/filters/network/common/fuzz/utils/fakes.h b/test/extensions/filters/network/common/fuzz/utils/fakes.h index 44193a979a9e0..4a4e134867af4 100644 --- a/test/extensions/filters/network/common/fuzz/utils/fakes.h +++ b/test/extensions/filters/network/common/fuzz/utils/fakes.h @@ -22,7 +22,7 @@ class FakeFactoryContext : public MockFactoryContext { Stats::Scope& scope() override { return scope_; } Singleton::Manager& singletonManager() override { return *singleton_manager_; } ThreadLocal::Instance& threadLocal() override { return thread_local_; } - Server::Admin& admin() override { return admin_; } + OptRef admin() override { return admin_; } Stats::Scope& listenerScope() override { return listener_scope_; } Api::Api& api() override { return *api_; } TimeSource& timeSource() override { return time_system_; } diff --git a/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_integration_test.cc b/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_integration_test.cc index cab3adf8c5704..4e2441b31bbae 100644 --- a/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_integration_test.cc +++ b/test/extensions/http/header_formatters/preserve_case/preserve_case_formatter_integration_test.cc @@ -1,5 +1,7 @@ #include "envoy/extensions/http/header_formatters/preserve_case/v3/preserve_case.pb.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + #include "test/integration/filters/common.h" #include "test/integration/http_integration.h" #include "test/test_common/registry.h" diff --git a/test/extensions/stats_sinks/hystrix/BUILD b/test/extensions/stats_sinks/hystrix/BUILD index 86c25935ecc42..bd85b3044316f 100644 --- a/test/extensions/stats_sinks/hystrix/BUILD +++ b/test/extensions/stats_sinks/hystrix/BUILD @@ -1,6 +1,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_package", + "envoy_select_admin_functionality", ) load( "//test/extensions:extensions_build_system.bzl", @@ -47,7 +48,7 @@ envoy_extension_cc_test( envoy_extension_cc_test( name = "hystrix_integration_test", - srcs = ["hystrix_integration_test.cc"], + srcs = envoy_select_admin_functionality(["hystrix_integration_test.cc"]), extension_names = ["envoy.stat_sinks.hystrix"], deps = [ "//source/extensions/stat_sinks/hystrix:config", diff --git a/test/extensions/transport_sockets/tls/integration/BUILD b/test/extensions/transport_sockets/tls/integration/BUILD index 81ebb9fd4188d..f1fae76ac3f3c 100644 --- a/test/extensions/transport_sockets/tls/integration/BUILD +++ b/test/extensions/transport_sockets/tls/integration/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_select_admin_functionality", ) licenses(["notice"]) # Apache 2 @@ -24,13 +25,11 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/network:connection_lib", "//source/common/network:utility_lib", - "//source/extensions/transport_sockets/tap:config", "//source/extensions/transport_sockets/tls:config", "//source/extensions/transport_sockets/tls:context_config_lib", "//source/extensions/transport_sockets/tls:context_lib", "//source/extensions/transport_sockets/tls:ssl_handshaker_lib", "//test/common/config:dummy_config_proto_cc_proto", - "//test/extensions/common/tap:common", "//test/extensions/transport_sockets/tls/cert_validator:timed_cert_validator", "//test/integration:http_integration_lib", "//test/integration/filters:stream_info_to_headers_filter_lib", @@ -38,12 +37,15 @@ envoy_cc_test( "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + ] + envoy_select_admin_functionality([ + "//source/extensions/transport_sockets/tap:config", + "//test/extensions/common/tap:common", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", "@envoy_api//envoy/data/tap/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/tap/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", - ], + ]), ) envoy_cc_test_library( diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index 5b9b30b17829a..31ab87e58c144 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -6,11 +6,7 @@ #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/tap/v3/common.pb.h" -#include "envoy/data/tap/v3/wrapper.pb.h" #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" -#include "envoy/extensions/transport_sockets/tap/v3/tap.pb.h" -#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "source/common/event/dispatcher_impl.h" #include "source/common/network/connection_impl.h" @@ -22,7 +18,6 @@ #include "source/extensions/transport_sockets/tls/ssl_socket.h" #include "test/common/config/dummy_config.pb.h" -#include "test/extensions/common/tap/common.h" #include "test/extensions/transport_sockets/tls/cert_validator/timed_cert_validator.h" #include "test/integration/autonomous_upstream.h" #include "test/integration/integration.h" @@ -37,6 +32,14 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#ifdef ENVOY_ADMIN_FUNCTIONALITY +#include "envoy/config/tap/v3/common.pb.h" +#include "envoy/data/tap/v3/wrapper.pb.h" +#include "envoy/extensions/transport_sockets/tap/v3/tap.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" +#include "test/extensions/common/tap/common.h" +#endif + namespace Envoy { using Extensions::TransportSockets::Tls::ContextImplPeer; @@ -343,6 +346,7 @@ TEST_P(SslIntegrationTest, RouterDownstreamDisconnectBeforeResponseComplete) { // This test must be here vs integration_admin_test so that it tests a server with loaded certs. TEST_P(SslIntegrationTest, AdminCertEndpoint) { + DISABLE_IF_ADMIN_DISABLED; // Admin functionality. initialize(); BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("admin"), "GET", "/certs", "", downstreamProtocol(), version_); @@ -860,6 +864,7 @@ TEST_P(SslCertficateIntegrationTest, BothEcdsaAndRsaWithOcspResponseStaplingRequ checkStats(); } +#ifdef ENVOY_ADMIN_FUNCTIONALITY // TODO(zuercher): write an additional OCSP integration test that validates behavior with an // expired OCSP response. (Requires OCSP client-side support in upstream TLS.) @@ -1171,6 +1176,7 @@ TEST_P(SslTapIntegrationTest, RequestWithStreamingUpstreamTap) { EXPECT_EQ(traces[2].socket_streamed_trace_segment().event().read().data().as_bytes(), "HTTP/"); EXPECT_TRUE(traces[2].socket_streamed_trace_segment().event().read().data().truncated()); } +#endif INSTANTIATE_TEST_SUITE_P(IpVersions, SslKeyLogTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), diff --git a/test/integration/BUILD b/test/integration/BUILD index 345ef2dae15cf..e4980f52b0d21 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -7,10 +7,11 @@ load( "envoy_cc_test_library", "envoy_package", "envoy_proto_library", + "envoy_select_admin_functionality", "envoy_select_enable_http3", - "envoy_select_hot_restart", "envoy_sh_test", ) +load("@bazel_skylib//lib:selects.bzl", "selects") licenses(["notice"]) # Apache 2 @@ -52,7 +53,9 @@ envoy_cc_test_library( envoy_cc_test( name = "ads_integration_test", size = "enormous", - srcs = ["ads_integration_test.cc"], + srcs = envoy_select_admin_functionality( + ["ads_integration_test.cc"], + ), deps = [ ":ads_integration_lib", ":http_integration_lib", @@ -274,9 +277,9 @@ envoy_cc_test_library( envoy_cc_test( name = "drain_close_integration_test", - srcs = [ + srcs = envoy_select_admin_functionality([ "drain_close_integration_test.cc", - ], + ]), shard_count = 2, deps = [ ":http_protocol_integration_lib", @@ -302,9 +305,15 @@ envoy_cc_test_binary( envoy_sh_test( name = "hotrestart_test", size = "enormous", - srcs = envoy_select_hot_restart([ - "hotrestart_test.sh", - ]), + srcs = selects.with_or({ + ( + "//bazel:disable_hot_restart", + "//bazel:disable_admin_functionality", + ): [], + "//conditions:default": [ + "hotrestart_test.sh", + ], + }), cc_binary = [":hotrestart_main"], data = [ "test_utility.sh", @@ -524,9 +533,9 @@ envoy_cc_test( envoy_cc_test( name = "http_typed_per_filter_config_test", - srcs = [ + srcs = envoy_select_admin_functionality([ "http_typed_per_filter_config_test.cc", - ], + ]), deps = [ ":http_integration_lib", "//test/integration/filters:set_response_code_filter_lib", @@ -548,9 +557,9 @@ envoy_cc_test( envoy_cc_test( name = "filter_integration_test", - srcs = [ + srcs = envoy_select_admin_functionality([ "filter_integration_test.cc", - ], + ]), shard_count = 12, deps = [ ":http_protocol_integration_lib", @@ -627,9 +636,9 @@ envoy_cc_test( envoy_cc_test( name = "multi_envoy_test", - srcs = [ + srcs = envoy_select_admin_functionality([ "multi_envoy_test.cc", - ], + ]), deps = [ ":http_protocol_integration_lib", ], @@ -655,10 +664,12 @@ envoy_cc_test( envoy_cc_test( name = "integration_admin_test", - srcs = [ - "integration_admin_test.cc", - "integration_admin_test.h", - ], + srcs = envoy_select_admin_functionality( + [ + "integration_admin_test.cc", + "integration_admin_test.h", + ], + ), deps = [ ":http_protocol_integration_lib", "//envoy/http:header_map_interface", @@ -864,6 +875,7 @@ envoy_cc_test_library( "base_integration_test.h", ], deps = [ + "//source/extensions/request_id/uuid:config", ":autonomous_upstream_lib", ":fake_upstream_lib", ":integration_tcp_client_lib", @@ -1258,7 +1270,9 @@ envoy_cc_test( envoy_cc_test( name = "rtds_integration_test", - srcs = ["rtds_integration_test.cc"], + srcs = envoy_select_admin_functionality([ + "rtds_integration_test.cc", + ]), deps = [ ":http_integration_lib", "//test/common/grpc:grpc_client_integration_lib", @@ -1509,10 +1523,10 @@ envoy_cc_test( envoy_cc_test( name = "uds_integration_test", - srcs = [ + srcs = envoy_select_admin_functionality([ "uds_integration_test.cc", "uds_integration_test.h", - ], + ]), deps = [ ":http_integration_lib", "//source/common/event:dispatcher_includes", @@ -1526,7 +1540,9 @@ envoy_cc_test( envoy_cc_test( name = "dynamic_validation_integration_test", - srcs = ["dynamic_validation_integration_test.cc"], + srcs = envoy_select_admin_functionality( + ["dynamic_validation_integration_test.cc"], + ), data = ["//test/config/integration:server_xds_files"], deps = [ ":http_integration_lib", @@ -1538,7 +1554,9 @@ envoy_cc_test( envoy_cc_test( name = "xds_integration_test", - srcs = ["xds_integration_test.cc"], + srcs = envoy_select_admin_functionality([ + "xds_integration_test.cc", + ]), data = [ "//test/config/integration:server_xds_files", "//test/config/integration/certs", @@ -1565,10 +1583,10 @@ envoy_cc_test( envoy_cc_test( name = "xfcc_integration_test", - srcs = [ + srcs = envoy_select_admin_functionality([ "xfcc_integration_test.cc", "xfcc_integration_test.h", - ], + ]), data = [ "//test/config/integration/certs", ], @@ -1869,22 +1887,28 @@ envoy_cc_test( envoy_cc_test( name = "quic_protocol_integration_test", size = "large", - srcs = envoy_select_enable_http3([ - "quic_protocol_integration_test.cc", - ]), + srcs = select({ + "//bazel:disable_http3": [], + "//bazel:disable_admin_functionality": [], + "//conditions:default": ["quic_protocol_integration_test.cc"], + }), data = ["//test/config/integration/certs"], shard_count = 12, tags = [ "nofips", ], - deps = envoy_select_enable_http3([ - ":protocol_integration_test_lib", - "//source/common/quic:active_quic_listener_lib", - "//source/common/quic:client_connection_factory_lib", - "//source/common/quic:quic_factory_lib", - "//source/common/quic:quic_transport_socket_factory_lib", - "//test/integration/filters:pause_filter_for_quic_lib", - ]), + deps = select({ + "//bazel:disable_http3": [], + "//bazel:disable_admin_functionality": [], + "//conditions:default": [ + ":protocol_integration_test_lib", + "//source/common/quic:active_quic_listener_lib", + "//source/common/quic:client_connection_factory_lib", + "//source/common/quic:quic_factory_lib", + "//source/common/quic:quic_transport_socket_factory_lib", + "//test/integration/filters:pause_filter_for_quic_lib", + ], + }), ) # TODO(mattklein123): Use target_compatible_with when we switch to Bazel 4.0 instead of multiple @@ -1892,7 +1916,11 @@ envoy_cc_test( envoy_cc_test( name = "quic_http_integration_test", size = "large", - srcs = envoy_select_enable_http3(["quic_http_integration_test.cc"]), + srcs = select({ + "//bazel:disable_http3": [], + "//bazel:disable_admin_functionality": [], + "//conditions:default": ["quic_http_integration_test.cc"], + }), data = ["//test/config/integration/certs"], shard_count = 6, # TODO(envoyproxy/windows-dev): Diagnose failure shown only on clang-cl build, see: @@ -1999,7 +2027,9 @@ envoy_cc_test( envoy_cc_test( name = "xds_delegate_extension_integration_test", - srcs = ["xds_delegate_extension_integration_test.cc"], + srcs = envoy_select_admin_functionality([ + "xds_delegate_extension_integration_test.cc", + ]), external_deps = ["abseil_strings"], deps = [ ":http_integration_lib", diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc index 8447e321f65f2..bff190ca146d2 100644 --- a/test/integration/ads_integration.cc +++ b/test/integration/ads_integration.cc @@ -291,19 +291,19 @@ void AdsIntegrationTest::testBasicFlow() { } envoy::admin::v3::ClustersConfigDump AdsIntegrationTest::getClustersConfigDump() { - auto message_ptr = test_server_->server().admin().getConfigTracker().getCallbacksMap().at( + auto message_ptr = test_server_->server().admin()->getConfigTracker().getCallbacksMap().at( "clusters")(Matchers::UniversalStringMatcher()); return dynamic_cast(*message_ptr); } envoy::admin::v3::ListenersConfigDump AdsIntegrationTest::getListenersConfigDump() { - auto message_ptr = test_server_->server().admin().getConfigTracker().getCallbacksMap().at( + auto message_ptr = test_server_->server().admin()->getConfigTracker().getCallbacksMap().at( "listeners")(Matchers::UniversalStringMatcher()); return dynamic_cast(*message_ptr); } envoy::admin::v3::RoutesConfigDump AdsIntegrationTest::getRoutesConfigDump() { - auto message_ptr = test_server_->server().admin().getConfigTracker().getCallbacksMap().at( + auto message_ptr = test_server_->server().admin()->getConfigTracker().getCallbacksMap().at( "routes")(Matchers::UniversalStringMatcher()); return dynamic_cast(*message_ptr); } diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 7ed1469f38a08..e276045ba6fa1 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -64,6 +64,11 @@ BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstrea // Allow extension lookup by name in the integration tests. config_helper_.addRuntimeOverride("envoy.reloadable_features.no_extension_lookup_by_name", "false"); + +#ifndef ENVOY_ADMIN_FUNCTIONALITY + config_helper_.addConfigModifier( + [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { bootstrap.clear_admin(); }); +#endif } BaseIntegrationTest::BaseIntegrationTest(Network::Address::IpVersion version, @@ -100,9 +105,11 @@ void BaseIntegrationTest::initialize() { createXdsUpstream(); createEnvoy(); +#ifdef ENVOY_ADMIN_FUNCTIONALITY if (!skip_tag_extraction_rule_check_) { checkForMissingTagExtractionRules(); } +#endif } Network::DownstreamTransportSocketFactoryPtr @@ -362,15 +369,17 @@ void BaseIntegrationTest::registerTestServerPorts(const std::vector } } } - const auto admin_addr = - test_server->server().admin().socket().connectionInfoProvider().localAddress(); - if (admin_addr->type() == Network::Address::Type::Ip) { - registerPort("admin", admin_addr->ip()->port()); + if (test_server->server().admin().has_value()) { + const auto admin_addr = + test_server->server().admin()->socket().connectionInfoProvider().localAddress(); + if (admin_addr->type() == Network::Address::Type::Ip) { + registerPort("admin", admin_addr->ip()->port()); + } } } std::string getListenerDetails(Envoy::Server::Instance& server) { - const auto& cbs_maps = server.admin().getConfigTracker().getCallbacksMap(); + const auto& cbs_maps = server.admin()->getConfigTracker().getCallbacksMap(); ProtobufTypes::MessagePtr details = cbs_maps.at("listeners")(Matchers::UniversalStringMatcher()); auto listener_info = Protobuf::down_cast(*details); return MessageUtil::getYamlStringFromMessage(listener_info.dynamic_listeners(0).error_state()); diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index 42f178017b84a..82cd22eeab4a3 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -41,6 +41,14 @@ } while (0) #endif +#ifndef ENVOY_ADMIN_FUNCTIONALITY +#define DISABLE_IF_ADMIN_DISABLED return +#else +#define DISABLE_IF_ADMIN_DISABLED \ + do { \ + } while (0) +#endif + namespace Envoy { struct ApiFilesystemConfig { diff --git a/test/integration/cx_limit_integration_test.cc b/test/integration/cx_limit_integration_test.cc index fd3c967b669c6..d2fe7e89b6cce 100644 --- a/test/integration/cx_limit_integration_test.cc +++ b/test/integration/cx_limit_integration_test.cc @@ -180,6 +180,7 @@ TEST_P(ConnectionLimitIntegrationTest, TestBothLimits) { } TEST_P(ConnectionLimitIntegrationTest, TestGlobalLimitOptOut) { + DISABLE_IF_ADMIN_DISABLED; // Requires admin config. // Includes 4 connections because the tracking is performed regardless of whether a specific // listener has opted out. Since the fake upstream has a listener, we need to keep value at 4 so // it can accept connections. (2 downstream listener conns + 2 upstream listener conns) diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index fda542ebbb137..f9e5b7027072c 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -497,7 +497,7 @@ TEST_P(IntegrationAdminTest, AdminOnDestroyCallbacks) { }; EXPECT_TRUE( - test_server_->server().admin().addHandler("/foo/bar", "hello", callback, true, false)); + test_server_->server().admin()->addHandler("/foo/bar", "hello", callback, true, false)); // As part of the request, on destroy() should be called and the on_destroy_callback invoked. BufferingStreamDecoderPtr response; diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index c18c30ec7cf14..ad6dd512a66a7 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -121,6 +121,7 @@ TEST_P(IntegrationTest, BadPostListenSocketOption) { // Make sure we have correctly specified per-worker performance stats. TEST_P(IntegrationTest, PerWorkerStatsAndBalancing) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin stats concurrency_ = 2; config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); @@ -177,6 +178,7 @@ class TestConnectionBalanceFactory : public Network::ConnectionBalanceFactory { // Test extend balance. TEST_P(IntegrationTest, ConnectionBalanceFactory) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin stats concurrency_ = 2; TestConnectionBalanceFactory factory; @@ -2113,6 +2115,7 @@ TEST_P(IntegrationTest, Response204WithBody) { } TEST_P(IntegrationTest, QuitQuitQuit) { + DISABLE_IF_ADMIN_DISABLED; // Uses admin interface. initialize(); test_server_->useAdminInterfaceToQuit(true); } diff --git a/test/integration/server.cc b/test/integration/server.cc index 2337e1ae44ec2..3a19040a98b5f 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -240,7 +240,9 @@ void IntegrationTestServerImpl::createAndRunEnvoyServer( // This is technically thread unsafe (assigning to a shared_ptr accessed // across threads), but because we synchronize below through serverReady(), the only // consumer on the main test thread in ~IntegrationTestServerImpl will not race. - admin_address_ = server.admin().socket().connectionInfoProvider().localAddress(); + if (server.admin()) { + admin_address_ = server.admin()->socket().connectionInfoProvider().localAddress(); + } server_ = &server; stat_store_ = &stat_store; serverReady(); diff --git a/test/mocks/server/admin.h b/test/mocks/server/admin.h index 960eb974d17d0..5d99e87e344aa 100644 --- a/test/mocks/server/admin.h +++ b/test/mocks/server/admin.h @@ -41,6 +41,7 @@ class MockAdmin : public Admin { Http::ResponseHeaderMap& response_headers, std::string& body)); MOCK_METHOD(void, addListenerToHandler, (Network::ConnectionHandler * handler)); MOCK_METHOD(uint32_t, concurrency, (), (const)); + MOCK_METHOD(void, closeSocket, ()); NiceMock config_tracker_; NiceMock socket_; diff --git a/test/mocks/server/factory_context.cc b/test/mocks/server/factory_context.cc index 1d2033d453a19..15fbbfb828068 100644 --- a/test/mocks/server/factory_context.cc +++ b/test/mocks/server/factory_context.cc @@ -32,7 +32,7 @@ MockFactoryContext::MockFactoryContext() ON_CALL(*this, serverScope()).WillByDefault(ReturnRef(scope_)); ON_CALL(*this, singletonManager()).WillByDefault(ReturnRef(*singleton_manager_)); ON_CALL(*this, threadLocal()).WillByDefault(ReturnRef(thread_local_)); - ON_CALL(*this, admin()).WillByDefault(ReturnRef(admin_)); + ON_CALL(*this, admin()).WillByDefault(Return(OptRef{admin_})); ON_CALL(*this, listenerScope()).WillByDefault(ReturnRef(listener_scope_)); ON_CALL(*this, api()).WillByDefault(ReturnRef(api_)); ON_CALL(*this, timeSource()).WillByDefault(ReturnRef(time_system_)); diff --git a/test/mocks/server/factory_context.h b/test/mocks/server/factory_context.h index 529a7519aa0a7..78bb69f32521a 100644 --- a/test/mocks/server/factory_context.h +++ b/test/mocks/server/factory_context.h @@ -37,7 +37,7 @@ class MockFactoryContext : public virtual ListenerFactoryContext { MOCK_METHOD(Singleton::Manager&, singletonManager, ()); MOCK_METHOD(OverloadManager&, overloadManager, ()); MOCK_METHOD(ThreadLocal::Instance&, threadLocal, ()); - MOCK_METHOD(Server::Admin&, admin, ()); + MOCK_METHOD(OptRef, admin, ()); MOCK_METHOD(Stats::Scope&, listenerScope, ()); MOCK_METHOD(bool, isQuicListener, (), (const)); MOCK_METHOD(const LocalInfo::LocalInfo&, localInfo, (), (const)); diff --git a/test/mocks/server/instance.cc b/test/mocks/server/instance.cc index ab6c7d9d70ea2..1d2bfcdf48289 100644 --- a/test/mocks/server/instance.cc +++ b/test/mocks/server/instance.cc @@ -29,7 +29,7 @@ MockInstance::MockInstance() ON_CALL(*this, routerContext()).WillByDefault(ReturnRef(router_context_)); ON_CALL(*this, dnsResolver()).WillByDefault(Return(dns_resolver_)); ON_CALL(*this, api()).WillByDefault(ReturnRef(api_)); - ON_CALL(*this, admin()).WillByDefault(ReturnRef(admin_)); + ON_CALL(*this, admin()).WillByDefault(Return(OptRef{admin_})); ON_CALL(*this, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); ON_CALL(*this, sslContextManager()).WillByDefault(ReturnRef(ssl_context_manager_)); ON_CALL(*this, accessLogManager()).WillByDefault(ReturnRef(access_log_manager_)); @@ -69,7 +69,7 @@ MockServerFactoryContext::MockServerFactoryContext() ON_CALL(*this, serverScope()).WillByDefault(ReturnRef(scope_)); ON_CALL(*this, singletonManager()).WillByDefault(ReturnRef(*singleton_manager_)); ON_CALL(*this, threadLocal()).WillByDefault(ReturnRef(thread_local_)); - ON_CALL(*this, admin()).WillByDefault(ReturnRef(admin_)); + ON_CALL(*this, admin()).WillByDefault(Return(OptRef{admin_})); ON_CALL(*this, api()).WillByDefault(ReturnRef(api_)); ON_CALL(*this, timeSource()).WillByDefault(ReturnRef(time_system_)); ON_CALL(*this, messageValidationContext()).WillByDefault(ReturnRef(validation_context_)); diff --git a/test/mocks/server/instance.h b/test/mocks/server/instance.h index a85008ebb01e6..2330c3feb59f4 100644 --- a/test/mocks/server/instance.h +++ b/test/mocks/server/instance.h @@ -46,9 +46,9 @@ class MockInstance : public Instance { MockInstance(); ~MockInstance() override; - Secret::SecretManager& secretManager() override { return *(secret_manager_.get()); } + Secret::SecretManager& secretManager() override { return *(secret_manager_); } - MOCK_METHOD(Admin&, admin, ()); + MOCK_METHOD(OptRef, admin, ()); MOCK_METHOD(Api::Api&, api, ()); MOCK_METHOD(Upstream::ClusterManager&, clusterManager, ()); MOCK_METHOD(const Upstream::ClusterManager&, clusterManager, (), (const)); @@ -161,7 +161,7 @@ class MockServerFactoryContext : public virtual ServerFactoryContext { MOCK_METHOD(Stats::Scope&, serverScope, ()); MOCK_METHOD(Singleton::Manager&, singletonManager, ()); MOCK_METHOD(ThreadLocal::Instance&, threadLocal, ()); - MOCK_METHOD(Server::Admin&, admin, ()); + MOCK_METHOD(OptRef, admin, ()); MOCK_METHOD(TimeSource&, timeSource, ()); Event::TestTimeSystem& timeSystem() { return time_system_; } MOCK_METHOD(ProtobufMessage::ValidationContext&, messageValidationContext, ()); diff --git a/test/mocks/server/listener_factory_context.cc b/test/mocks/server/listener_factory_context.cc index a604a4bec5a68..696bd391cb834 100644 --- a/test/mocks/server/listener_factory_context.cc +++ b/test/mocks/server/listener_factory_context.cc @@ -31,7 +31,7 @@ MockListenerFactoryContext::MockListenerFactoryContext() ON_CALL(*this, serverScope()).WillByDefault(ReturnRef(scope_)); ON_CALL(*this, singletonManager()).WillByDefault(ReturnRef(*singleton_manager_)); ON_CALL(*this, threadLocal()).WillByDefault(ReturnRef(thread_local_)); - ON_CALL(*this, admin()).WillByDefault(ReturnRef(admin_)); + ON_CALL(*this, admin()).WillByDefault(Return(OptRef{admin_})); ON_CALL(*this, listenerScope()).WillByDefault(ReturnRef(listener_scope_)); ON_CALL(*this, api()).WillByDefault(ReturnRef(api_)); ON_CALL(*this, timeSource()).WillByDefault(ReturnRef(time_system_)); diff --git a/test/mocks/server/listener_factory_context.h b/test/mocks/server/listener_factory_context.h index 0047446ba7745..7bd95203b2195 100644 --- a/test/mocks/server/listener_factory_context.h +++ b/test/mocks/server/listener_factory_context.h @@ -39,7 +39,7 @@ class MockListenerFactoryContext : public ListenerFactoryContext { MOCK_METHOD(Singleton::Manager&, singletonManager, ()); MOCK_METHOD(OverloadManager&, overloadManager, ()); MOCK_METHOD(ThreadLocal::Instance&, threadLocal, ()); - MOCK_METHOD(Server::Admin&, admin, ()); + MOCK_METHOD(OptRef, admin, ()); MOCK_METHOD(Stats::Scope&, listenerScope, ()); MOCK_METHOD(bool, isQuicListener, (), (const)); MOCK_METHOD(const LocalInfo::LocalInfo&, localInfo, (), (const)); diff --git a/test/mocks/server/transport_socket_factory_context.h b/test/mocks/server/transport_socket_factory_context.h index 4b6d5b1fdeb5a..283f84892965b 100644 --- a/test/mocks/server/transport_socket_factory_context.h +++ b/test/mocks/server/transport_socket_factory_context.h @@ -22,9 +22,9 @@ class MockTransportSocketFactoryContext : public TransportSocketFactoryContext { MockTransportSocketFactoryContext(); ~MockTransportSocketFactoryContext() override; - Secret::SecretManager& secretManager() override { return *(secret_manager_.get()); } + Secret::SecretManager& secretManager() override { return *(secret_manager_); } - MOCK_METHOD(Server::Admin&, admin, ()); + MOCK_METHOD(OptRef, admin, ()); MOCK_METHOD(Ssl::ContextManager&, sslContextManager, ()); MOCK_METHOD(Stats::Scope&, scope, ()); MOCK_METHOD(Upstream::ClusterManager&, clusterManager, ()); diff --git a/test/server/BUILD b/test/server/BUILD index ff97e19616031..271b6b9bfe179 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -6,6 +6,7 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_select_admin_functionality", "envoy_select_hot_restart", ) load("//source/extensions:all_extensions.bzl", "envoy_all_extensions") @@ -413,7 +414,7 @@ filegroup( envoy_cc_test( name = "server_test", - srcs = ["server_test.cc"], + srcs = envoy_select_admin_functionality(["server_test.cc"]), data = [ ":runtime_test_data", ":server_test_data", diff --git a/test/server/admin/BUILD b/test/server/admin/BUILD index 35684d1b00382..7eae434ab61c9 100644 --- a/test/server/admin/BUILD +++ b/test/server/admin/BUILD @@ -4,6 +4,7 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_select_admin_functionality", "envoy_select_admin_html", ) @@ -13,8 +14,8 @@ envoy_package() envoy_cc_test_library( name = "admin_instance_lib", - srcs = ["admin_instance.cc"], - hdrs = ["admin_instance.h"], + srcs = envoy_select_admin_functionality(["admin_instance.cc"]), + hdrs = envoy_select_admin_functionality(["admin_instance.h"]), deps = [ "//source/server/admin:admin_lib", "//test/mocks/runtime:runtime_mocks", @@ -27,7 +28,7 @@ envoy_cc_test_library( envoy_cc_test( name = "admin_test", - srcs = ["admin_test.cc"], + srcs = envoy_select_admin_functionality(["admin_test.cc"]), deps = [ ":admin_instance_lib", "//envoy/json:json_object_interface", @@ -49,9 +50,8 @@ envoy_cc_test( envoy_cc_test( name = "admin_filter_test", - srcs = ["admin_filter_test.cc"], + srcs = envoy_select_admin_functionality(["admin_filter_test.cc"]), deps = [ - "//source/server/admin:admin_filter_lib", "//source/server/admin:admin_lib", "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", @@ -69,13 +69,12 @@ envoy_cc_test( envoy_cc_test( name = "stats_handler_test", - srcs = ["stats_handler_test.cc"], + srcs = envoy_select_admin_functionality(["stats_handler_test.cc"]), deps = [ ":admin_instance_lib", "//source/common/common:regex_lib", "//source/common/stats:thread_local_store_lib", "//source/common/thread_local:thread_local_lib", - "//source/server/admin:stats_handler_lib", "//source/server/admin:utils_lib", "//test/mocks/server:admin_stream_mocks", "//test/test_common:logging_lib", @@ -87,7 +86,7 @@ envoy_cc_test( envoy_cc_test( name = "stats_params_test", - srcs = ["stats_params_test.cc"], + srcs = envoy_select_admin_functionality(["stats_params_test.cc"]), deps = [ "//source/common/buffer:buffer_lib", "//source/server/admin:stats_params_lib", @@ -97,7 +96,7 @@ envoy_cc_test( envoy_cc_test( name = "stats_render_test", - srcs = ["stats_render_test.cc"] + envoy_select_admin_html([ + srcs = envoy_select_admin_functionality(["stats_render_test.cc"]) + envoy_select_admin_html([ "stats_html_render_test.cc", ]), deps = [ @@ -108,7 +107,7 @@ envoy_cc_test( envoy_cc_test_library( name = "stats_render_test_base", - srcs = ["stats_render_test_base.cc"], + srcs = envoy_select_admin_functionality(["stats_render_test_base.cc"]), hdrs = ["stats_render_test_base.h"], deps = [ "//source/common/stats:thread_local_store_lib", @@ -122,7 +121,7 @@ envoy_cc_test_library( envoy_cc_test( name = "stats_request_test", - srcs = ["stats_request_test.cc"], + srcs = envoy_select_admin_functionality(["stats_request_test.cc"]), deps = [ "//source/common/stats:thread_local_store_lib", "//source/server/admin:stats_request_lib", @@ -135,20 +134,19 @@ envoy_cc_test( envoy_cc_benchmark_binary( name = "stats_handler_speed_test", - srcs = ["stats_handler_speed_test.cc"], + srcs = envoy_select_admin_functionality(["stats_handler_speed_test.cc"]), deps = [ "//source/common/buffer:buffer_lib", "//source/common/http:header_map_lib", "//source/common/stats:thread_local_store_lib", "//source/server/admin:admin_lib", - "//source/server/admin:stats_handler_lib", "//test/test_common:test_runtime_lib", ], ) envoy_cc_test( name = "utils_test", - srcs = ["utils_test.cc"], + srcs = envoy_select_admin_functionality(["utils_test.cc"]), deps = [ "//source/server/admin:utils_lib", ], @@ -156,13 +154,13 @@ envoy_cc_test( envoy_cc_test( name = "runtime_handler_test", - srcs = ["runtime_handler_test.cc"], + srcs = envoy_select_admin_functionality(["runtime_handler_test.cc"]), deps = [":admin_instance_lib"], ) envoy_cc_test( name = "prometheus_stats_test", - srcs = ["prometheus_stats_test.cc"], + srcs = envoy_select_admin_functionality(["prometheus_stats_test.cc"]), deps = [ "//source/server/admin:prometheus_stats_lib", "//test/test_common:utility_lib", @@ -171,7 +169,7 @@ envoy_cc_test( envoy_cc_test( name = "logs_handler_test", - srcs = ["logs_handler_test.cc"], + srcs = envoy_select_admin_functionality(["logs_handler_test.cc"]), deps = [ ":admin_instance_lib", "//source/common/common:minimal_logger_lib", @@ -180,7 +178,7 @@ envoy_cc_test( envoy_cc_test( name = "profiling_handler_test", - srcs = ["profiling_handler_test.cc"], + srcs = envoy_select_admin_functionality(["profiling_handler_test.cc"]), deps = [ ":admin_instance_lib", "//test/test_common:logging_lib", @@ -189,7 +187,7 @@ envoy_cc_test( envoy_cc_test( name = "server_info_handler_test", - srcs = ["server_info_handler_test.cc"], + srcs = envoy_select_admin_functionality(["server_info_handler_test.cc"]), deps = [ ":admin_instance_lib", "//source/extensions/transport_sockets/tls:context_config_lib", @@ -201,7 +199,7 @@ envoy_cc_test( envoy_cc_test( name = "clusters_handler_test", - srcs = ["clusters_handler_test.cc"], + srcs = envoy_select_admin_functionality(["clusters_handler_test.cc"]), deps = [ ":admin_instance_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", @@ -210,7 +208,7 @@ envoy_cc_test( envoy_cc_test( name = "config_dump_handler_test", - srcs = ["config_dump_handler_test.cc"], + srcs = envoy_select_admin_functionality(["config_dump_handler_test.cc"]), deps = [ ":admin_instance_lib", ], @@ -218,7 +216,7 @@ envoy_cc_test( envoy_cc_test( name = "init_dump_handler_test", - srcs = ["init_dump_handler_test.cc"], + srcs = envoy_select_admin_functionality(["init_dump_handler_test.cc"]), deps = [ ":admin_instance_lib", ], @@ -226,7 +224,7 @@ envoy_cc_test( envoy_cc_test( name = "config_tracker_impl_test", - srcs = ["config_tracker_impl_test.cc"], + srcs = envoy_select_admin_functionality(["config_tracker_impl_test.cc"]), deps = [ "//source/common/common:matchers_lib", "//source/server/admin:config_tracker_lib", diff --git a/test/server/config_validation/xds_fuzz.cc b/test/server/config_validation/xds_fuzz.cc index 9b91aaae53b73..43ec342341c40 100644 --- a/test/server/config_validation/xds_fuzz.cc +++ b/test/server/config_validation/xds_fuzz.cc @@ -386,13 +386,13 @@ void XdsFuzzTest::verifyState() { } envoy::admin::v3::ListenersConfigDump XdsFuzzTest::getListenersConfigDump() { - auto message_ptr = test_server_->server().admin().getConfigTracker().getCallbacksMap().at( + auto message_ptr = test_server_->server().admin()->getConfigTracker().getCallbacksMap().at( "listeners")(Matchers::UniversalStringMatcher()); return dynamic_cast(*message_ptr); } std::vector XdsFuzzTest::getRoutesConfigDump() { - auto map = test_server_->server().admin().getConfigTracker().getCallbacksMap(); + auto map = test_server_->server().admin()->getConfigTracker().getCallbacksMap(); // There is no route config dump before envoy has a route. if (map.find("routes") == map.end()) { diff --git a/test/server/config_validation/xds_fuzz_test.cc b/test/server/config_validation/xds_fuzz_test.cc index 52c2d583e4adb..547de485dc6ab 100644 --- a/test/server/config_validation/xds_fuzz_test.cc +++ b/test/server/config_validation/xds_fuzz_test.cc @@ -3,8 +3,8 @@ #include "test/server/config_validation/xds_fuzz.pb.validate.h" namespace Envoy { - DEFINE_PROTO_FUZZER(const test::server::config_validation::XdsTestCase& input) { +#ifdef ENVOY_ADMIN_FUNCTIONALITY RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), ""); try { TestUtility::validate(input); @@ -16,6 +16,8 @@ DEFINE_PROTO_FUZZER(const test::server::config_validation::XdsTestCase& input) { test.replay(); XdsFuzzTest test_with_unified_mux(input, true); test_with_unified_mux.replay(); +#else + UNREFERENCED_PARAMETER(input); +#endif } - } // namespace Envoy diff --git a/test/server/server_test.cc b/test/server/server_test.cc index ac327a962e950..3b24e2feccd2a 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -807,7 +807,7 @@ TEST_P(ServerInstanceImplTest, FlushStatsOnAdmin) { // Flush via admin. Http::TestResponseHeaderMapImpl response_headers; std::string body; - EXPECT_EQ(Http::Code::OK, server_->admin().request("/stats", "GET", response_headers, body)); + EXPECT_EQ(Http::Code::OK, server_->admin()->request("/stats", "GET", response_headers, body)); EXPECT_EQ(1L, counter->value()); time_system_.advanceTimeWait(std::chrono::seconds(6)); @@ -1052,11 +1052,11 @@ TEST_P(ServerInstanceImplTest, RuntimeNoAdminLayer) { Http::TestResponseHeaderMapImpl response_headers; std::string response_body; EXPECT_EQ(Http::Code::OK, - server_->admin().request("/runtime", "GET", response_headers, response_body)); + server_->admin()->request("/runtime", "GET", response_headers, response_body)); EXPECT_THAT(response_body, HasSubstr("fozz")); - EXPECT_EQ( - Http::Code::ServiceUnavailable, - server_->admin().request("/runtime_modify?foo=bar", "POST", response_headers, response_body)); + EXPECT_EQ(Http::Code::ServiceUnavailable, + server_->admin()->request("/runtime_modify?foo=bar", "POST", response_headers, + response_body)); EXPECT_EQ("No admin layer specified", response_body); } @@ -1157,7 +1157,7 @@ TEST_P(ServerInstanceImplTest, BootstrapNodeNoAdmin) { // Admin::addListenerToHandler() calls one of handler's methods after checking that the Admin // has a listener. So, the fact that passing a nullptr doesn't cause a segfault establishes // that there is no listener. - server_->admin().addListenerToHandler(/*handler=*/nullptr); + server_->admin()->addListenerToHandler(/*handler=*/nullptr); } namespace { @@ -1179,7 +1179,7 @@ TEST_P(ServerInstanceImplTest, BootstrapNodeWithSocketOptions) { // Start Envoy instance with admin port with SO_REUSEPORT option. ASSERT_NO_THROW( initialize("test/server/test_data/server/node_bootstrap_with_admin_socket_options.yaml")); - const auto address = server_->admin().socket().connectionInfoProvider().localAddress(); + const auto address = server_->admin()->socket().connectionInfoProvider().localAddress(); // First attempt to bind and listen socket should fail due to the lack of SO_REUSEPORT socket // options. From e3f01ee540e88ea1c52b6eb9a2ece258f1d12e3c Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Thu, 10 Nov 2022 09:23:53 -0500 Subject: [PATCH 2/7] comments Signed-off-by: Alyssa Wilk --- bazel/README.md | 2 +- ci/do_ci.sh | 2 +- source/common/config/config_provider_impl.cc | 15 ++++++++------- .../common/rds/route_config_provider_manager.cc | 15 ++++++++------- source/exe/main_common.cc | 7 ++++--- source/extensions/stat_sinks/hystrix/hystrix.cc | 11 ++++++----- source/server/config_validation/server.h | 2 +- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index 72ea22f5a30f1..8a696e51e7063 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -689,7 +689,7 @@ The following optional features can be disabled on the Bazel build command-line: * http3/quic with --//bazel:http3=False * autolinking libraries with --define=library_autolink=disabled * admin HTML home page with `--define=admin_html=disabled` -* admin functionlity with `--define=admin_functionaity=disabled` +* admin functionality with `--define=admin_functionality=disabled` ## Enabling optional features diff --git a/ci/do_ci.sh b/ci/do_ci.sh index f8f2754a43de0..7900adde918d2 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -396,7 +396,7 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then echo "Building and testing with wasm=wamr: ${TEST_TARGETS[*]}" bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wamr "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only - echo "Building and testing with wasm=wasmtime: and admin_functionality disabled ${TEST_TARGETS[*]}" + echo "Building and testing with wasm=wasmtime: and admin_functionality and admin_html disabled ${TEST_TARGETS[*]}" bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wasmtime --define admin_html=disabled --define admin_functionality=disabled "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" diff --git a/source/common/config/config_provider_impl.cc b/source/common/config/config_provider_impl.cc index fc58e02091fd7..48888e519055b 100644 --- a/source/common/config/config_provider_impl.cc +++ b/source/common/config/config_provider_impl.cc @@ -52,14 +52,15 @@ bool ConfigSubscriptionInstance::checkAndApplyConfigUpdate(const Protobuf::Messa ConfigProviderManagerImplBase::ConfigProviderManagerImplBase(OptRef admin, const std::string& config_name) { - if (admin.has_value()) { - config_tracker_entry_ = admin->getConfigTracker().add( - config_name, - [this](const Matchers::StringMatcher& name_matcher) { return dumpConfigs(name_matcher); }); - // ConfigTracker keys must be unique. We are asserting that no one has stolen the key - // from us, since the returned entry will be nullptr if the key already exists. - RELEASE_ASSERT(config_tracker_entry_, ""); + if (!admin.has_value()) { + return; } + config_tracker_entry_ = admin->getConfigTracker().add( + config_name, + [this](const Matchers::StringMatcher& name_matcher) { return dumpConfigs(name_matcher); }); + // ConfigTracker keys must be unique. We are asserting that no one has stolen the key + // from us, since the returned entry will be nullptr if the key already exists. + RELEASE_ASSERT(config_tracker_entry_, ""); } const ConfigProviderManagerImplBase::ConfigProviderSet& diff --git a/source/common/rds/route_config_provider_manager.cc b/source/common/rds/route_config_provider_manager.cc index f31ee868eaf9a..9db2b37865e3d 100644 --- a/source/common/rds/route_config_provider_manager.cc +++ b/source/common/rds/route_config_provider_manager.cc @@ -9,14 +9,15 @@ RouteConfigProviderManager::RouteConfigProviderManager(OptRef adm const std::string& config_tracker_key, ProtoTraits& proto_traits) : proto_traits_(proto_traits) { - if (admin.has_value()) { - config_tracker_entry_ = admin->getConfigTracker().add( - config_tracker_key, - [this](const Matchers::StringMatcher& matcher) { return dumpRouteConfigs(matcher); }); - // ConfigTracker keys must be unique. We are asserting that no one has stolen the "routes" key - // from us, since the returned entry will be nullptr if the key already exists. - RELEASE_ASSERT(config_tracker_entry_, ""); + if (!admin.has_value()) { + return; } + config_tracker_entry_ = admin->getConfigTracker().add( + config_tracker_key, + [this](const Matchers::StringMatcher& matcher) { return dumpRouteConfigs(matcher); }); + // ConfigTracker keys must be unique. We are asserting that no one has stolen the "routes" key + // from us, since the returned entry will be nullptr if the key already exists. + RELEASE_ASSERT(config_tracker_entry_, ""); } void RouteConfigProviderManager::eraseStaticProvider(RouteConfigProvider* provider) { diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index 7bbeef5447307..04cb530dd9540 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -186,14 +186,15 @@ bool MainCommonBase::run() { void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method, const AdminRequestFn& handler) { - RELEASE_ASSERT(server_->admin().has_value(), - "adminRequest called with admin support compiled out"); + ASSERT(server_->admin().has_value(), "adminRequest called with admin support compiled out"); std::string path_and_query_buf = std::string(path_and_query); std::string method_buf = std::string(method); server_->dispatcher().post([this, path_and_query_buf, method_buf, handler]() { auto response_headers = Http::ResponseHeaderMapImpl::create(); std::string body; - server_->admin()->request(path_and_query_buf, method_buf, *response_headers, body); + if (server_->admin()) { + server_->admin()->request(path_and_query_buf, method_buf, *response_headers, body); + } handler(*response_headers, body); }); } diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index dc1609bff4027..c912c42cf2ab7 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -283,12 +283,13 @@ HystrixSink::HystrixSink(Server::Configuration::ServerFactoryContext& server, upstream_rq_2xx_(stat_name_pool_.add("upstream_rq_2xx")), upstream_rq_4xx_(stat_name_pool_.add("upstream_rq_4xx")), upstream_rq_5xx_(stat_name_pool_.add("upstream_rq_5xx")) { - if (server.admin().has_value()) { - ENVOY_LOG(debug, - "adding hystrix_event_stream endpoint to enable connection to hystrix dashboard"); - server.admin()->addHandler("/hystrix_event_stream", "send hystrix event stream", - MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); + if (!server.admin().has_value()) { + return; } + ENVOY_LOG(debug, + "adding hystrix_event_stream endpoint to enable connection to hystrix dashboard"); + server.admin()->addHandler("/hystrix_event_stream", "send hystrix event stream", + MAKE_ADMIN_HANDLER(handlerHystrixEventStream), false, false); } Http::Code HystrixSink::handlerHystrixEventStream(Http::ResponseHeaderMap& response_headers, diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index f23501dc60e17..96208a3fed059 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -71,7 +71,7 @@ class ValidationInstance final : Logger::Loggable, // Server::Instance OptRef admin() override { - return makeOptRefFromPtr(reinterpret_cast(admin_.get())); + return makeOptRefFromPtr(static_cast(admin_.get())); } Api::Api& api() override { return *api_; } Upstream::ClusterManager& clusterManager() override { return *config_.clusterManager(); } From a1d5c1ad510dd18a3e58ccd5be57c63c8c3f7ae3 Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Thu, 10 Nov 2022 13:29:19 -0500 Subject: [PATCH 3/7] attempt better bazel Signed-off-by: Alyssa Wilk --- bazel/BUILD | 8 ++++++++ test/integration/BUILD | 12 +++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index c0a2d600da836..3dd8e1c39a05d 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -306,6 +306,14 @@ config_setting( values = {"define": "deprecated_features=disabled"}, ) +selects.config_setting_group( + name = "disable_hot_restart_or_admin", + match_any = [ + "//bazel:disable_hot_restart", + "//bazel:disable_admin_functionality", + ], +) + bool_flag( name = "http3", build_setting_default = True, diff --git a/test/integration/BUILD b/test/integration/BUILD index 2f9045e0202b6..4e90755fc00c4 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -11,7 +11,6 @@ load( "envoy_select_enable_http3", "envoy_sh_test", ) -load("@bazel_skylib//lib:selects.bzl", "selects") licenses(["notice"]) # Apache 2 @@ -307,14 +306,9 @@ envoy_cc_test_binary( envoy_sh_test( name = "hotrestart_test", size = "enormous", - srcs = selects.with_or({ - ( - "//bazel:disable_hot_restart", - "//bazel:disable_admin_functionality", - ): [], - "//conditions:default": [ - "hotrestart_test.sh", - ], + srcs = select({ + "//bazel:disable_hot_restart_or_admin": [], + "//conditions:default": ["hotrestart_test.sh"], }), cc_binary = [":hotrestart_main"], data = [ From dba9757353da0f8e3f2227bd2e1329bd261df6db Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Thu, 10 Nov 2022 16:09:11 -0500 Subject: [PATCH 4/7] ifdef Signed-off-by: Alyssa Wilk --- source/exe/main_common.cc | 3 ++- source/exe/main_common.h | 4 ++++ test/integration/base_integration_test.cc | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index 04cb530dd9540..ea84f4037ba6e 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -184,9 +184,9 @@ bool MainCommonBase::run() { return false; // for gcc. } +#ifdef ENVOY_ADMIN_FUNCTIONALITY void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string_view method, const AdminRequestFn& handler) { - ASSERT(server_->admin().has_value(), "adminRequest called with admin support compiled out"); std::string path_and_query_buf = std::string(path_and_query); std::string method_buf = std::string(method); server_->dispatcher().post([this, path_and_query_buf, method_buf, handler]() { @@ -198,6 +198,7 @@ void MainCommonBase::adminRequest(absl::string_view path_and_query, absl::string handler(*response_headers, body); }); } +#endif MainCommon::MainCommon(const std::vector& args) : options_(args, &MainCommon::hotRestartVersion, spdlog::level::info), diff --git a/source/exe/main_common.h b/source/exe/main_common.h index fde86ecafe412..f8dff2b702123 100644 --- a/source/exe/main_common.h +++ b/source/exe/main_common.h @@ -45,6 +45,7 @@ class MainCommonBase { // Will be null if options.mode() == Server::Mode::Validate Server::Instance* server() { return server_.get(); } +#ifdef ENVOY_ADMIN_FUNCTIONALITY using AdminRequestFn = std::function; @@ -62,6 +63,7 @@ class MainCommonBase { // semantics, rather than a handler callback. void adminRequest(absl::string_view path_and_query, absl::string_view method, const AdminRequestFn& handler); +#endif protected: std::unique_ptr platform_impl_; @@ -106,6 +108,7 @@ class MainCommon { // Only tests have a legitimate need for this today. Event::Dispatcher& dispatcherForTest() { return base_.server()->dispatcher(); } +#ifdef ENVOY_ADMIN_FUNCTIONALITY // Makes an admin-console request by path, calling handler() when complete. // The caller can initiate this from any thread, but it posts the request // onto the main thread, so the handler is called asynchronously. @@ -119,6 +122,7 @@ class MainCommon { const MainCommonBase::AdminRequestFn& handler) { base_.adminRequest(path_and_query, method, handler); } +#endif static std::string hotRestartVersion(bool hot_restart_enabled); diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index b805eb2cf69c8..03754542ce42d 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -7,6 +7,7 @@ #include #include +#include "absl/strings/str_format.h" #include "envoy/admin/v3/config_dump.pb.h" #include "envoy/buffer/buffer.h" #include "envoy/config/bootstrap/v3/bootstrap.pb.h" From 4a3f628a12b9309dd600ca6a4a35cb0ec7ed504e Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Tue, 15 Nov 2022 08:44:29 -0500 Subject: [PATCH 5/7] format Signed-off-by: Alyssa Wilk --- test/integration/base_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 03754542ce42d..41a3b7a543d1c 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -7,7 +7,6 @@ #include #include -#include "absl/strings/str_format.h" #include "envoy/admin/v3/config_dump.pb.h" #include "envoy/buffer/buffer.h" #include "envoy/config/bootstrap/v3/bootstrap.pb.h" @@ -26,6 +25,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "absl/strings/str_format.h" #include "gtest/gtest.h" namespace Envoy { From 50c5abada43c70acea6da9fbd6ecfdd0c9515951 Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Tue, 15 Nov 2022 15:32:47 -0500 Subject: [PATCH 6/7] merge/compiletime fix Signed-off-by: Alyssa Wilk --- str_f | 5860 +++++++++++++++++++++ test/integration/base_integration_test.cc | 1 - test/integration/base_integration_test.h | 1 + 3 files changed, 5861 insertions(+), 1 deletion(-) create mode 100644 str_f diff --git a/str_f b/str_f new file mode 100644 index 0000000000000..7238f36b6e677 --- /dev/null +++ b/str_f @@ -0,0 +1,5860 @@ +diff --git a/.bazelrc b/.bazelrc +index ce5e826be6..79da7e60c4 100644 +--- a/.bazelrc ++++ b/.bazelrc +@@ -159,7 +159,9 @@ build:libc++ --action_env=BAZEL_LINKOPTS=-lm:-pthread + build:libc++ --define force_libcpp=enabled +  + # Optimize build for binary size reduction. +-build:sizeopt -c opt --copt -Os --linkopt=-Wl,--gc-sections --linkopt=-Wl,--dead_strip ++build:sizeopt-sections -c opt --copt -Os --linkopt=-Wl,--gc-sections ++build:sizeopt -c opt --copt -Os ++build:sizeopt-strip -c opt --copt -Os --linkopt=-Wl,-dead_strip +  + # Test options + build --test_env=HEAPCHECK=normal --test_env=PPROF_PATH +diff --git a/.github/workflows/depsreview.yml b/.github/workflows/depsreview.yml +index f8aac61052..ca7fc7d313 100644 +--- a/.github/workflows/depsreview.yml ++++ b/.github/workflows/depsreview.yml +@@ -9,4 +9,4 @@ jobs: + - name: 'Checkout Repository' + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - name: 'Dependency Review' +- uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c ++ uses: actions/dependency-review-action@30d582111533d59ab793fd9f971817241654f3ec +diff --git a/CODEOWNERS b/CODEOWNERS +index 749a89644b..672dc4a717 100644 +--- a/CODEOWNERS ++++ b/CODEOWNERS +@@ -164,6 +164,8 @@ extensions/filters/http/oauth2 @derekargueta @snowp + /*/extensions/matching/input_matchers/consistent_hashing @snowp @donyu + # environment generic input + /*/extensions/matching/common_inputs/environment @snowp @donyu ++# format string matching ++/*/extensions/matching/actions/format_string @kyessenov @UNOWNED + # user space socket pair, event, connection and listener + /*/extensions/io_socket/user_space @kyessenov @antoniovicente + /*/extensions/bootstrap/internal_listener @kyessenov @adisuissa +diff --git a/api/envoy/config/endpoint/v3/endpoint_components.proto b/api/envoy/config/endpoint/v3/endpoint_components.proto +index 59e699ae64..247f2a5019 100644 +--- a/api/envoy/config/endpoint/v3/endpoint_components.proto ++++ b/api/envoy/config/endpoint/v3/endpoint_components.proto +@@ -51,6 +51,10 @@ message Endpoint { + // + // The form of the health check host address is expected to be a direct IP address. + core.v3.Address address = 3; ++ ++ // Optional flag to control if perform active health check for this endpoint. ++ // Active health check is enabled by default if there is a health checker. ++ bool disable_active_health_check = 4; + } +  + // The upstream host address. +diff --git a/api/envoy/config/rbac/v3/rbac.proto b/api/envoy/config/rbac/v3/rbac.proto +index e4299789e7..e8d9e9cf8b 100644 +--- a/api/envoy/config/rbac/v3/rbac.proto ++++ b/api/envoy/config/rbac/v3/rbac.proto +@@ -5,6 +5,7 @@ package envoy.config.rbac.v3; + import "envoy/config/core/v3/address.proto"; + import "envoy/config/core/v3/extension.proto"; + import "envoy/config/route/v3/route_components.proto"; ++import "envoy/type/matcher/v3/filter_state.proto"; + import "envoy/type/matcher/v3/metadata.proto"; + import "envoy/type/matcher/v3/path.proto"; + import "envoy/type/matcher/v3/string.proto"; +@@ -229,7 +230,7 @@ message Permission { +  + // Principal defines an identity or a group of identities for a downstream + // subject. +-// [#next-free-field: 12] ++// [#next-free-field: 13] + message Principal { + option (udpa.annotations.versioning).previous_message_type = "envoy.config.rbac.v2.Principal"; +  +@@ -304,6 +305,9 @@ message Principal { + // Metadata that describes additional information about the principal. + type.matcher.v3.MetadataMatcher metadata = 7; +  ++ // Identifies the principal using a filter state object. ++ type.matcher.v3.FilterStateMatcher filter_state = 12; ++ + // Negates matching the provided principal. For instance, if the value of + // ``not_id`` would match, this principal would not match. Conversely, if the + // value of ``not_id`` would not match, this principal would match. +diff --git a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto +index 8288684fbf..132ce3a4eb 100644 +--- a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto ++++ b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto +@@ -157,6 +157,11 @@ message ExternalProcessor { + // with the header prefix set via + // :ref:`header_prefix ` + // (which is usually "x-envoy"). ++ // Note that changing headers such as "host" or ":authority" may not in itself ++ // change Envoy's routing decision, as routes can be cached. To also force the ++ // route to be recomputed, set the ++ // :ref:`clear_route_cache ` ++ // field to true in the same response. + config.common.mutation_rules.v3.HeaderMutationRules mutation_rules = 9; + } +  +diff --git a/api/envoy/type/matcher/v3/filter_state.proto b/api/envoy/type/matcher/v3/filter_state.proto +new file mode 100644 +index 0000000000..f813178ae0 +--- /dev/null ++++ b/api/envoy/type/matcher/v3/filter_state.proto +@@ -0,0 +1,29 @@ ++syntax = "proto3"; ++ ++package envoy.type.matcher.v3; ++ ++import "envoy/type/matcher/v3/string.proto"; ++ ++import "udpa/annotations/status.proto"; ++import "validate/validate.proto"; ++ ++option java_package = "io.envoyproxy.envoy.type.matcher.v3"; ++option java_outer_classname = "FilterStateProto"; ++option java_multiple_files = true; ++option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3;matcherv3"; ++option (udpa.annotations.file_status).package_version_status = ACTIVE; ++ ++// [#protodoc-title: Filter state matcher] ++ ++// FilterStateMatcher provides a general interface for matching the filter state objects. ++message FilterStateMatcher { ++ // The filter state key to retrieve the object. ++ string key = 1 [(validate.rules).string = {min_len: 1}]; ++ ++ oneof matcher { ++ option (validate.required) = true; ++ ++ // Matches the filter state object as a string value. ++ StringMatcher string_match = 2; ++ } ++} +diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl +index 57f2cc7c0b..de733b15e5 100644 +--- a/bazel/envoy_build_system.bzl ++++ b/bazel/envoy_build_system.bzl +@@ -49,6 +49,7 @@ load( + "@envoy_build_config//:extensions_build_config.bzl", + "CONTRIB_EXTENSION_PACKAGE_VISIBILITY", + "EXTENSION_PACKAGE_VISIBILITY", ++ "MOBILE_PACKAGE_VISIBILITY", + ) + load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") +  +@@ -69,7 +70,9 @@ def envoy_extension_package(enabled_default = True, default_visibility = EXTENSI + ) +  + def envoy_mobile_package(): +- envoy_extension_package() ++ # Mobile packages should only be visible to other mobile packages, not any other ++ # parts of the Envoy codebase. ++ envoy_extension_package(default_visibility = MOBILE_PACKAGE_VISIBILITY) +  + def envoy_contrib_package(): + envoy_extension_package(default_visibility = CONTRIB_EXTENSION_PACKAGE_VISIBILITY) +diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl +index 60bb366f0c..6c6c0e0914 100644 +--- a/bazel/repository_locations.bzl ++++ b/bazel/repository_locations.bzl +@@ -1080,12 +1080,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( + 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", +- version = "c6efbc4f790a274f1f4030cd8437683a321f23b8", +- sha256 = "7911438519437af82356c76a9b96a8a61fcb3ee5c11ca7e6f190e4bca53c3cb0", ++ version = "8b0d15bda8fdeb80a40ef53b7932b0897025dc11", ++ sha256 = "b03b0c8e1c7d261ebb1865f9c9c7a3e6e14bc382203526e5bf767c7fed0f9c16", + urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], + strip_prefix = "quiche-{version}", + use_category = ["dataplane_core"], +- release_date = "2022-11-02", ++ release_date = "2022-11-10", + cpe = "N/A", + license = "BSD-3-Clause", + license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", +diff --git a/changelogs/current.yaml b/changelogs/current.yaml +index b11b2363c2..ee03fcf329 100644 +--- a/changelogs/current.yaml ++++ b/changelogs/current.yaml +@@ -113,9 +113,15 @@ new_features: + - area: udp_proxy + change: | + added support for :ref:`proxy_access_log `. ++- area: health_check ++ change: | ++ added an optional bool flag :ref:`disable_active_health_check ` to disable the active health check for the endpoint. + - area: mobile + change: | + started merging the Envoy mobile library into the main Envoy repo. ++- area: matching ++ change: | ++ support filter chain selection based on the dynamic metadata and the filter state using :ref:`formatter actions `. + - area: redis + change: | + extended :ref:`cluster support ` by adding a :ref:`dns_cache_config ` option that can be used to resolve hostnames returned by MOVED/ASK responses. +diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy +index 1b736accba..c80c0c5923 100644 +--- a/ci/Dockerfile-envoy ++++ b/ci/Dockerfile-envoy +@@ -42,7 +42,7 @@ CMD ["envoy", "-c", "/etc/envoy/envoy.yaml"] +  + # STAGE: envoy-distroless + # gcr.io/distroless/base-debian11:nonroot +-FROM gcr.io/distroless/base-debian11@sha256:49d2923f35d66b8402487a7c01bc62a66d8279cd42e89c11b64cdce8d5826c03 AS envoy-distroless ++FROM gcr.io/distroless/base-debian11:nonroot@sha256:4b22ca3c68018333c56f8dddcf1f8b55f32889f2dd12d28ab60856eba1130d04 AS envoy-distroless +  + COPY --from=binary /usr/local/bin/envoy* /usr/local/bin/ + COPY --from=binary /usr/local/bin/su-exec /usr/local/bin/ +diff --git a/ci/osx-build-config/extensions_build_config.bzl b/ci/osx-build-config/extensions_build_config.bzl +index d0ff8fdfac..79a960e731 100644 +--- a/ci/osx-build-config/extensions_build_config.bzl ++++ b/ci/osx-build-config/extensions_build_config.bzl +@@ -17,6 +17,7 @@ WINDOWS_EXTENSIONS = {} + EXTENSION_CONFIG_VISIBILITY = ["//:extension_config"] + EXTENSION_PACKAGE_VISIBILITY = ["//:extension_library"] + CONTRIB_EXTENSION_PACKAGE_VISIBILITY = ["//:contrib_library"] ++MOBILE_PACKAGE_VISIBILITY = ["//:mobile_library"] +  + # As part of (https://github.com/envoyproxy/envoy-mobile/issues/175) we turned down alwayslink for envoy libraries + # This tracks libraries that should be registered as extensions. +diff --git a/docs/root/api-docs/diagrams/envoy-perf-script.svg b/docs/root/api-docs/diagrams/envoy-perf-script.svg +deleted file mode 100644 +index 74759e1482..0000000000 +Binary files a/docs/root/api-docs/diagrams/envoy-perf-script.svg and /dev/null differ +diff --git a/docs/root/api-v3/types/types.rst b/docs/root/api-v3/types/types.rst +index 081171a162..6ebcc0b551 100644 +--- a/docs/root/api-v3/types/types.rst ++++ b/docs/root/api-v3/types/types.rst +@@ -12,6 +12,7 @@ Types + ../type/v3/http_status.proto + ../type/http/v3/cookie.proto + ../type/metadata/v3/metadata.proto ++ ../type/matcher/v3/filter_state.proto + ../type/matcher/v3/metadata.proto + ../type/matcher/v3/node.proto + ../type/matcher/v3/number.proto +diff --git a/docs/root/intro/arch_overview/advanced/matching/matching_api.rst b/docs/root/intro/arch_overview/advanced/matching/matching_api.rst +index ec72c0253d..721a3a6a0e 100644 +--- a/docs/root/intro/arch_overview/advanced/matching/matching_api.rst ++++ b/docs/root/intro/arch_overview/advanced/matching/matching_api.rst +@@ -86,6 +86,28 @@ are available in some contexts: +  + * :ref:`Trie-based IP matcher ` applies to network inputs. +  ++Matching actions ++################ ++ ++The action in the matcher framework typically refers to the selected resource by name. ++ ++Network filter chain matching supports the following extensions: ++ ++.. _extension_envoy.matching.actions.format_string: ++ ++* :ref:`Format string action ` computes the filter chain name ++ from the connection dynamic metadata and its filter state. Example: ++ ++.. validated-code-block:: yaml ++ :type-name: envoy.config.common.matcher.v3.Matcher.OnMatch ++ ++ action: ++ name: foo ++ typed_config: ++ "@type": type.googleapis.com/envoy.config.core.v3.SubstitutionFormatString ++ text_format_source: ++ inline_string: "%DYNAMIC_METADATA(com.test_filter:test_key)%" ++ + Filter Integration + ################## +  +diff --git a/envoy/formatter/substitution_formatter.h b/envoy/formatter/substitution_formatter.h +index 99f00ecc38..c0d1502c19 100644 +--- a/envoy/formatter/substitution_formatter.h ++++ b/envoy/formatter/substitution_formatter.h +@@ -36,6 +36,7 @@ public: + }; +  + using FormatterPtr = std::unique_ptr; ++using FormatterConstSharedPtr = std::shared_ptr; +  + /** + * Interface for substitution provider. +diff --git a/envoy/http/filter.h b/envoy/http/filter.h +index 10ba0ec44c..c625bfe925 100644 +--- a/envoy/http/filter.h ++++ b/envoy/http/filter.h +@@ -172,7 +172,7 @@ enum class FilterMetadataStatus { + * Return codes for onLocalReply filter invocations. + */ + enum class LocalErrorStatus { +- // Continue sending the local reply after onLocalError has been sent to all filters. ++ // Continue sending the local reply after onLocalReply has been sent to all filters. + Continue, +  + // Continue sending onLocalReply to all filters, but reset the stream once all filters have been +@@ -781,7 +781,7 @@ public: + * from onLocalReply, as that has the potential for looping. + * + * @param data data associated with the sendLocalReply call. +- * @param LocalErrorStatus the action to take after onLocalError completes. ++ * @param LocalErrorStatus the action to take after onLocalReply completes. + */ + virtual LocalErrorStatus onLocalReply(const LocalReplyData&) { + return LocalErrorStatus::Continue; +diff --git a/envoy/upstream/cluster_manager.h b/envoy/upstream/cluster_manager.h +index bbdcaf2711..fbb50d1736 100644 +--- a/envoy/upstream/cluster_manager.h ++++ b/envoy/upstream/cluster_manager.h +@@ -378,7 +378,10 @@ public: + * + * @return the stat names. + */ +- virtual const ClusterStatNames& clusterStatNames() const PURE; ++ virtual const ClusterTrafficStatNames& clusterStatNames() const PURE; ++ virtual const ClusterConfigUpdateStatNames& clusterConfigUpdateStatNames() const PURE; ++ virtual const ClusterLbStatNames& clusterLbStatNames() const PURE; ++ virtual const ClusterEndpointStatNames& clusterEndpointStatNames() const PURE; + virtual const ClusterLoadReportStatNames& clusterLoadReportStatNames() const PURE; + virtual const ClusterCircuitBreakersStatNames& clusterCircuitBreakersStatNames() const PURE; + virtual const ClusterRequestResponseSizeStatNames& +diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h +index 2633fc60f7..69b600b484 100644 +--- a/envoy/upstream/upstream.h ++++ b/envoy/upstream/upstream.h +@@ -242,6 +242,16 @@ public: + * connection pools no longer need this host. + */ + virtual HostHandlePtr acquireHandle() const PURE; ++ ++ /** ++ * @return true if active health check is disabled. ++ */ ++ virtual bool disableActiveHealthCheck() const PURE; ++ ++ /** ++ * Set true to disable active health check for the host. ++ */ ++ virtual void setDisableActiveHealthCheck(bool disable_active_health_check) PURE; + }; +  + using HostConstSharedPtr = std::shared_ptr; +@@ -565,12 +575,36 @@ public: + }; +  + /** +- * All cluster stats. @see stats_macros.h ++ * All cluster config update related stats. ++ * See https://github.com/envoyproxy/envoy/issues/23575 for details. Stats from ClusterInfo::stats() ++ * will be split into subgroups "config-update", "lb", "endpoint" and "the rest"(which are mainly ++ * upstream related), roughly based on their semantics. + */ +-#define ALL_CLUSTER_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ ++#define ALL_CLUSTER_CONFIG_UPDATE_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ + COUNTER(assignment_stale) \ + COUNTER(assignment_timeout_received) \ +- COUNTER(bind_errors) \ ++ COUNTER(update_attempt) \ ++ COUNTER(update_empty) \ ++ COUNTER(update_failure) \ ++ COUNTER(update_no_rebuild) \ ++ COUNTER(update_success) \ ++ GAUGE(version, NeverImport) ++ ++/** ++ * All cluster endpoints related stats. ++ */ ++#define ALL_CLUSTER_ENDPOINT_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ ++ GAUGE(max_host_weight, NeverImport) \ ++ COUNTER(membership_change) \ ++ GAUGE(membership_degraded, NeverImport) \ ++ GAUGE(membership_excluded, NeverImport) \ ++ GAUGE(membership_healthy, NeverImport) \ ++ GAUGE(membership_total, NeverImport) ++ ++/** ++ * All cluster loadbalancing related stats. ++ */ ++#define ALL_CLUSTER_LB_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ + COUNTER(lb_healthy_panic) \ + COUNTER(lb_local_cluster_not_ok) \ + COUNTER(lb_recalculate_zone_structures) \ +@@ -585,14 +619,15 @@ public: + COUNTER(lb_zone_routing_all_directly) \ + COUNTER(lb_zone_routing_cross_zone) \ + COUNTER(lb_zone_routing_sampled) \ +- COUNTER(membership_change) \ ++ GAUGE(lb_subsets_active, Accumulate) ++ ++/** ++ * All cluster stats. @see stats_macros.h ++ */ ++#define ALL_CLUSTER_TRAFFIC_STATS(COUNTER, GAUGE, HISTOGRAM, TEXT_READOUT, STATNAME) \ ++ COUNTER(bind_errors) \ + COUNTER(original_dst_host_invalid) \ + COUNTER(retry_or_shadow_abandoned) \ +- COUNTER(update_attempt) \ +- COUNTER(update_empty) \ +- COUNTER(update_failure) \ +- COUNTER(update_no_rebuild) \ +- COUNTER(update_success) \ + COUNTER(upstream_cx_close_notify) \ + COUNTER(upstream_cx_connect_attempts_exceeded) \ + COUNTER(upstream_cx_connect_fail) \ +@@ -644,18 +679,11 @@ public: + COUNTER(upstream_rq_total) \ + COUNTER(upstream_rq_tx_reset) \ + COUNTER(upstream_http3_broken) \ +- GAUGE(lb_subsets_active, Accumulate) \ +- GAUGE(max_host_weight, NeverImport) \ +- GAUGE(membership_degraded, NeverImport) \ +- GAUGE(membership_excluded, NeverImport) \ +- GAUGE(membership_healthy, NeverImport) \ +- GAUGE(membership_total, NeverImport) \ + GAUGE(upstream_cx_active, Accumulate) \ + GAUGE(upstream_cx_rx_bytes_buffered, Accumulate) \ + GAUGE(upstream_cx_tx_bytes_buffered, Accumulate) \ + GAUGE(upstream_rq_active, Accumulate) \ + GAUGE(upstream_rq_pending_active, Accumulate) \ +- GAUGE(version, NeverImport) \ + HISTOGRAM(upstream_cx_connect_ms, Milliseconds) \ + HISTOGRAM(upstream_cx_length_ms, Milliseconds) +  +@@ -708,10 +736,29 @@ public: + HISTOGRAM(upstream_rq_timeout_budget_per_try_percent_used, Unspecified) +  + /** +- * Struct definition for all cluster stats. @see stats_macros.h ++ * Struct definition for cluster config update stats. @see stats_macros.h ++ */ ++MAKE_STAT_NAMES_STRUCT(ClusterConfigUpdateStatNames, ALL_CLUSTER_CONFIG_UPDATE_STATS); ++MAKE_STATS_STRUCT(ClusterConfigUpdateStats, ClusterConfigUpdateStatNames, ++ ALL_CLUSTER_CONFIG_UPDATE_STATS); ++ ++/** ++ * Struct definition for cluster endpoint related stats. @see stats_macros.h ++ */ ++MAKE_STAT_NAMES_STRUCT(ClusterEndpointStatNames, ALL_CLUSTER_ENDPOINT_STATS); ++MAKE_STATS_STRUCT(ClusterEndpointStats, ClusterEndpointStatNames, ALL_CLUSTER_ENDPOINT_STATS); ++ ++/** ++ * Struct definition for cluster load balancing stats. @see stats_macros.h + */ +-MAKE_STAT_NAMES_STRUCT(ClusterStatNames, ALL_CLUSTER_STATS); +-MAKE_STATS_STRUCT(ClusterStats, ClusterStatNames, ALL_CLUSTER_STATS); ++MAKE_STAT_NAMES_STRUCT(ClusterLbStatNames, ALL_CLUSTER_LB_STATS); ++MAKE_STATS_STRUCT(ClusterLbStats, ClusterLbStatNames, ALL_CLUSTER_LB_STATS); ++ ++/** ++ * Struct definition for all cluster traffic stats. @see stats_macros.h ++ */ ++MAKE_STAT_NAMES_STRUCT(ClusterTrafficStatNames, ALL_CLUSTER_TRAFFIC_STATS); ++MAKE_STATS_STRUCT(ClusterTrafficStats, ClusterTrafficStatNames, ALL_CLUSTER_TRAFFIC_STATS); +  + MAKE_STAT_NAMES_STRUCT(ClusterLoadReportStatNames, ALL_CLUSTER_LOAD_REPORT_STATS); + MAKE_STATS_STRUCT(ClusterLoadReportStats, ClusterLoadReportStatNames, +@@ -992,9 +1039,24 @@ public: + virtual TransportSocketMatcher& transportSocketMatcher() const PURE; +  + /** +- * @return ClusterStats& strongly named stats for this cluster. ++ * @return ClusterConfigUpdateStats& config update stats for this cluster. ++ */ ++ virtual ClusterConfigUpdateStats& configUpdateStats() const PURE; ++ ++ /** ++ * @return ClusterLbStats& load-balancer-related stats for this cluster. ++ */ ++ virtual ClusterLbStats& lbStats() const PURE; ++ ++ /** ++ * @return ClusterEndpointStats& endpoint related stats for this cluster. ++ */ ++ virtual ClusterEndpointStats& endpointStats() const PURE; ++ ++ /** ++ * @return ClusterTrafficStats& all traffic related stats for this cluster. + */ +- virtual ClusterStats& stats() const PURE; ++ virtual ClusterTrafficStats& trafficStats() const PURE; +  + /** + * @return the stats scope that contains all cluster stats. This can be used to produce dynamic +@@ -1003,7 +1065,7 @@ public: + virtual Stats::Scope& statsScope() const PURE; +  + /** +- * @return ClusterLoadReportStats& strongly named load report stats for this cluster. ++ * @return ClusterLoadReportStats& load report stats for this cluster. + */ + virtual ClusterLoadReportStats& loadReportStats() const PURE; +  +diff --git a/examples/ext_authz/auth/grpc-service/Dockerfile b/examples/ext_authz/auth/grpc-service/Dockerfile +index 99d1ac5926..778497f4c2 100644 +--- a/examples/ext_authz/auth/grpc-service/Dockerfile ++++ b/examples/ext_authz/auth/grpc-service/Dockerfile +@@ -1,4 +1,4 @@ +-FROM golang:alpine@sha256:8558ae624304387d18694b9ea065cc9813dd4f7f9bd5073edb237541f2d0561b AS builder ++FROM golang:alpine@sha256:dc4f4756a4fb91b6f496a958e11e00c0621130c8dfbb31ac0737b0229ad6ad9c AS builder +  + RUN apk --no-cache add make + COPY . /app +diff --git a/examples/ext_authz/auth/http-service/Dockerfile b/examples/ext_authz/auth/http-service/Dockerfile +index 28c511b2d1..805d3ed04e 100644 +--- a/examples/ext_authz/auth/http-service/Dockerfile ++++ b/examples/ext_authz/auth/http-service/Dockerfile +@@ -1,4 +1,4 @@ +-FROM node:alpine@sha256:00c5c0850a48bbbf0136f1c886bad52784f9816a8d314a99307d734598359ed4 ++FROM node:alpine@sha256:083a23fe246cc82294f64e154f5d6bce8c90b9fc8f2dce54d3c58d41ddd8f8c8 +  + COPY . /app + CMD ["node", "/app/http-service/server"] +diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile +index daa795e062..4129a88250 100644 +--- a/examples/shared/postgres/Dockerfile ++++ b/examples/shared/postgres/Dockerfile +@@ -1 +1 @@ +-FROM postgres:latest@sha256:bab8d7be6466e029f7fa1e69ff6aa0082704db330572638fd01f2791824774d8 ++FROM postgres:latest@sha256:9eb2589e67e69daf321fa95ae40e7509ce08bb1ef90d5a27a0775aa88ee0c704 +diff --git a/source/common/common/matchers.cc b/source/common/common/matchers.cc +index 021e5e9a64..c1355f35f2 100644 +--- a/source/common/common/matchers.cc ++++ b/source/common/common/matchers.cc +@@ -96,6 +96,33 @@ MetadataMatcher::MetadataMatcher(const envoy::type::matcher::v3::MetadataMatcher + value_matcher_ = ValueMatcher::create(v); + } +  ++namespace { ++StringMatcherPtr ++valueMatcherFromProto(const envoy::type::matcher::v3::FilterStateMatcher& matcher) { ++ switch (matcher.matcher_case()) { ++ case envoy::type::matcher::v3::FilterStateMatcher::MatcherCase::kStringMatch: ++ return std::make_unique>( ++ matcher.string_match()); ++ break; ++ default: ++ PANIC_DUE_TO_PROTO_UNSET; ++ } ++} ++ ++} // namespace ++ ++FilterStateMatcher::FilterStateMatcher(const envoy::type::matcher::v3::FilterStateMatcher& matcher) ++ : key_(matcher.key()), value_matcher_(valueMatcherFromProto(matcher)) {} ++ ++bool FilterStateMatcher::match(const StreamInfo::FilterState& filter_state) const { ++ const auto* object = filter_state.getDataReadOnlyGeneric(key_); ++ if (object == nullptr) { ++ return false; ++ } ++ const auto string_value = object->serializeAsString(); ++ return string_value && value_matcher_->match(*string_value); ++} ++ + PathMatcherConstSharedPtr PathMatcher::createExact(const std::string& exact, bool ignore_case) { + envoy::type::matcher::v3::StringMatcher matcher; + matcher.set_exact(exact); +diff --git a/source/common/common/matchers.h b/source/common/common/matchers.h +index 34a8730dbd..214c628b64 100644 +--- a/source/common/common/matchers.h ++++ b/source/common/common/matchers.h +@@ -6,6 +6,7 @@ + #include "envoy/common/matchers.h" + #include "envoy/common/regex.h" + #include "envoy/config/core/v3/base.pb.h" ++#include "envoy/type/matcher/v3/filter_state.pb.h" + #include "envoy/type/matcher/v3/metadata.pb.h" + #include "envoy/type/matcher/v3/number.pb.h" + #include "envoy/type/matcher/v3/path.pb.h" +@@ -189,6 +190,22 @@ private: + ValueMatcherConstSharedPtr value_matcher_; + }; +  ++class FilterStateMatcher { ++public: ++ FilterStateMatcher(const envoy::type::matcher::v3::FilterStateMatcher& matcher); ++ ++ /** ++ * Check whether the filter state object is matched to the matcher. ++ * @param filter state to check. ++ * @return true if it's matched otherwise false. ++ */ ++ bool match(const StreamInfo::FilterState& filter_state) const; ++ ++private: ++ const std::string key_; ++ const StringMatcherPtr value_matcher_; ++}; ++ + class PathMatcher : public StringMatcher { + public: + PathMatcher(const envoy::type::matcher::v3::PathMatcher& path) : matcher_(path.path()) {} +diff --git a/source/common/conn_pool/conn_pool_base.cc b/source/common/conn_pool/conn_pool_base.cc +index 78d26a13f9..31ee3f3aff 100644 +--- a/source/common/conn_pool/conn_pool_base.cc ++++ b/source/common/conn_pool/conn_pool_base.cc +@@ -135,7 +135,7 @@ ConnPoolImplBase::tryCreateNewConnection(float global_preconnect_ratio) { + const bool can_create_connection = host_->canCreateConnection(priority_); +  + if (!can_create_connection) { +- host_->cluster().stats().upstream_cx_overflow_.inc(); ++ host_->cluster().trafficStats().upstream_cx_overflow_.inc(); + } + // If we are at the connection circuit-breaker limit due to other upstreams having + // too many open connections, and this upstream has no connections, always create one, to +@@ -168,14 +168,14 @@ void ConnPoolImplBase::attachStreamToClient(Envoy::ConnectionPool::ActiveClient& + ASSERT(client.readyForStream()); +  + if (client.state() == Envoy::ConnectionPool::ActiveClient::State::ReadyForEarlyData) { +- host_->cluster().stats().upstream_rq_0rtt_.inc(); ++ host_->cluster().trafficStats().upstream_rq_0rtt_.inc(); + } +  + if (enforceMaxRequests() && !host_->cluster().resourceManager(priority_).requests().canCreate()) { + ENVOY_LOG(debug, "max streams overflow"); + onPoolFailure(client.real_host_description_, absl::string_view(), + ConnectionPool::PoolFailureReason::Overflow, context); +- host_->cluster().stats().upstream_rq_pending_overflow_.inc(); ++ host_->cluster().trafficStats().upstream_rq_pending_overflow_.inc(); + return; + } + ENVOY_CONN_LOG(debug, "creating stream", client); +@@ -185,7 +185,7 @@ void ConnPoolImplBase::attachStreamToClient(Envoy::ConnectionPool::ActiveClient& + client.remaining_streams_--; + if (client.remaining_streams_ == 0) { + ENVOY_CONN_LOG(debug, "maximum streams per connection, start draining", client); +- host_->cluster().stats().upstream_cx_max_requests_.inc(); ++ host_->cluster().trafficStats().upstream_cx_max_requests_.inc(); + transitionActiveClientState(client, Envoy::ConnectionPool::ActiveClient::State::Draining); + } else if (capacity == 1) { + // As soon as the new stream is created, the client will be maxed out. +@@ -202,8 +202,8 @@ void ConnPoolImplBase::attachStreamToClient(Envoy::ConnectionPool::ActiveClient& + num_active_streams_++; + host_->stats().rq_total_.inc(); + host_->stats().rq_active_.inc(); +- host_->cluster().stats().upstream_rq_total_.inc(); +- host_->cluster().stats().upstream_rq_active_.inc(); ++ host_->cluster().trafficStats().upstream_rq_total_.inc(); ++ host_->cluster().trafficStats().upstream_rq_active_.inc(); + host_->cluster().resourceManager(priority_).requests().inc(); +  + onPoolReady(client, context); +@@ -216,7 +216,7 @@ void ConnPoolImplBase::onStreamClosed(Envoy::ConnectionPool::ActiveClient& clien + state_.decrActiveStreams(1); + num_active_streams_--; + host_->stats().rq_active_.dec(); +- host_->cluster().stats().upstream_rq_active_.dec(); ++ host_->cluster().trafficStats().upstream_rq_active_.dec(); + host_->cluster().resourceManager(priority_).requests().dec(); + // We don't update the capacity for HTTP/3 as the stream count should only + // increase when a MAX_STREAMS frame is received. +@@ -282,7 +282,7 @@ ConnectionPool::Cancellable* ConnPoolImplBase::newStreamImpl(AttachContext& cont + ENVOY_LOG(debug, "max pending streams overflow"); + onPoolFailure(nullptr, absl::string_view(), ConnectionPool::PoolFailureReason::Overflow, + context); +- host_->cluster().stats().upstream_rq_pending_overflow_.inc(); ++ host_->cluster().trafficStats().upstream_rq_pending_overflow_.inc(); + return nullptr; + } +  +@@ -490,7 +490,7 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view +  + if (!client.hasHandshakeCompleted()) { + client.has_handshake_completed_ = true; +- host_->cluster().stats().upstream_cx_connect_fail_.inc(); ++ host_->cluster().trafficStats().upstream_cx_connect_fail_.inc(); + host_->stats().cx_connect_fail_.inc(); +  + onConnectFailed(client); +@@ -595,7 +595,7 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view + client.currentUnusedCapacity()); + // No need to update connecting capacity and connect_timer_ as the client is still connecting. + ASSERT(client.state() == ActiveClient::State::Connecting); +- host()->cluster().stats().upstream_cx_connect_with_0_rtt_.inc(); ++ host()->cluster().trafficStats().upstream_cx_connect_with_0_rtt_.inc(); + transitionActiveClientState(client, (client.currentUnusedCapacity() > 0 + ? ActiveClient::State::ReadyForEarlyData + : ActiveClient::State::Busy)); +@@ -606,13 +606,13 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view +  + PendingStream::PendingStream(ConnPoolImplBase& parent, bool can_send_early_data) + : parent_(parent), can_send_early_data_(can_send_early_data) { +- parent_.host()->cluster().stats().upstream_rq_pending_total_.inc(); +- parent_.host()->cluster().stats().upstream_rq_pending_active_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_rq_pending_total_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_rq_pending_active_.inc(); + parent_.host()->cluster().resourceManager(parent_.priority()).pendingRequests().inc(); + } +  + PendingStream::~PendingStream() { +- parent_.host()->cluster().stats().upstream_rq_pending_active_.dec(); ++ parent_.host()->cluster().trafficStats().upstream_rq_pending_active_.dec(); + parent_.host()->cluster().resourceManager(parent_.priority()).pendingRequests().dec(); + } +  +@@ -630,7 +630,7 @@ void ConnPoolImplBase::purgePendingStreams( + while (!pending_streams_to_purge_.empty()) { + PendingStreamPtr stream = + pending_streams_to_purge_.front()->removeFromList(pending_streams_to_purge_); +- host_->cluster().stats().upstream_rq_pending_failure_eject_.inc(); ++ host_->cluster().trafficStats().upstream_rq_pending_failure_eject_.inc(); + onPoolFailure(host_description, failure_reason, reason, stream->context()); + } + } +@@ -683,7 +683,7 @@ void ConnPoolImplBase::onPendingStreamCancel(PendingStream& stream, + } + } +  +- host_->cluster().stats().upstream_rq_cancelled_.inc(); ++ host_->cluster().trafficStats().upstream_rq_cancelled_.inc(); + checkForIdleAndCloseIdleConnsIfDraining(); + } +  +@@ -757,14 +757,16 @@ ActiveClient::ActiveClient(ConnPoolImplBase& parent, uint32_t lifetime_stream_li + concurrent_stream_limit_(translateZeroToUnlimited(concurrent_stream_limit)), + connect_timer_(parent_.dispatcher().createTimer([this]() { onConnectTimeout(); })) { + conn_connect_ms_ = std::make_unique( +- parent_.host()->cluster().stats().upstream_cx_connect_ms_, parent_.dispatcher().timeSource()); ++ parent_.host()->cluster().trafficStats().upstream_cx_connect_ms_, ++ parent_.dispatcher().timeSource()); + conn_length_ = std::make_unique( +- parent_.host()->cluster().stats().upstream_cx_length_ms_, parent_.dispatcher().timeSource()); ++ parent_.host()->cluster().trafficStats().upstream_cx_length_ms_, ++ parent_.dispatcher().timeSource()); + connect_timer_->enableTimer(parent_.host()->cluster().connectTimeout()); + parent_.host()->stats().cx_total_.inc(); + parent_.host()->stats().cx_active_.inc(); +- parent_.host()->cluster().stats().upstream_cx_total_.inc(); +- parent_.host()->cluster().stats().upstream_cx_active_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_cx_total_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_cx_active_.inc(); + parent_.host()->cluster().resourceManager(parent_.priority()).connections().inc(); + } +  +@@ -776,7 +778,7 @@ void ActiveClient::releaseResourcesBase() { +  + conn_length_->complete(); +  +- parent_.host()->cluster().stats().upstream_cx_active_.dec(); ++ parent_.host()->cluster().trafficStats().upstream_cx_active_.dec(); + parent_.host()->stats().cx_active_.dec(); + parent_.host()->cluster().resourceManager(parent_.priority()).connections().dec(); + } +@@ -784,7 +786,7 @@ void ActiveClient::releaseResourcesBase() { +  + void ActiveClient::onConnectTimeout() { + ENVOY_CONN_LOG(debug, "connect timeout", *this); +- parent_.host()->cluster().stats().upstream_cx_connect_timeout_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_cx_connect_timeout_.inc(); + timed_out_ = true; + close(); + } +@@ -809,7 +811,7 @@ void ActiveClient::onConnectionDurationTimeout() { + } +  + ENVOY_CONN_LOG(debug, "max connection duration reached, start draining", *this); +- parent_.host()->cluster().stats().upstream_cx_max_duration_reached_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.inc(); + parent_.transitionActiveClientState(*this, Envoy::ConnectionPool::ActiveClient::State::Draining); +  + // Close out the draining client if we no longer have active streams. +diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc +index 104d72afc9..fead55e225 100644 +--- a/source/common/event/dispatcher_impl.cc ++++ b/source/common/event/dispatcher_impl.cc +@@ -151,8 +151,8 @@ DispatcherImpl::createServerConnection(Network::ConnectionSocketPtr&& socket, + Network::TransportSocketPtr&& transport_socket, + StreamInfo::StreamInfo& stream_info) { + ASSERT(isThreadSafe()); +- return std::make_unique( +- *this, std::move(socket), std::move(transport_socket), stream_info, true); ++ return std::make_unique(*this, std::move(socket), ++ std::move(transport_socket), stream_info); + } +  + Network::ClientConnectionPtr DispatcherImpl::createClientConnection( +diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc +index e7e8008ef1..19c1110618 100644 +--- a/source/common/http/codec_client.cc ++++ b/source/common/http/codec_client.cc +@@ -171,7 +171,7 @@ void CodecClient::onData(Buffer::Instance& data) { + if (!isPrematureResponseError(status) || + (!active_requests_.empty() || + getPrematureResponseHttpCode(status) != Code::RequestTimeout)) { +- host_->cluster().stats().upstream_cx_protocol_error_.inc(); ++ host_->cluster().trafficStats().upstream_cx_protocol_error_.inc(); + protocol_error_ = true; + } + close(); +diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h +index c53fbf5c34..6dee7081f4 100644 +--- a/source/common/http/codec_client.h ++++ b/source/common/http/codec_client.h +@@ -164,7 +164,7 @@ protected: + } +  + void onIdleTimeout() { +- host_->cluster().stats().upstream_cx_idle_timeout_.inc(); ++ host_->cluster().trafficStats().upstream_cx_idle_timeout_.inc(); + close(); + } +  +diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc +index 284e479a86..0ed8e5ef1c 100644 +--- a/source/common/http/conn_pool_base.cc ++++ b/source/common/http/conn_pool_base.cc +@@ -99,7 +99,7 @@ static const uint64_t DEFAULT_MAX_STREAMS = (1 << 29); +  + void MultiplexedActiveClientBase::onGoAway(Http::GoAwayErrorCode) { + ENVOY_CONN_LOG(debug, "remote goaway", *codec_client_); +- parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_cx_close_notify_.inc(); + if (state() != ActiveClient::State::Draining) { + if (codec_client_->numActiveRequests() == 0) { + codec_client_->close(); +@@ -160,16 +160,16 @@ void MultiplexedActiveClientBase::onStreamReset(Http::StreamResetReason reason) + switch (reason) { + case StreamResetReason::ConnectionTermination: + case StreamResetReason::ConnectionFailure: +- parent_.host()->cluster().stats().upstream_rq_pending_failure_eject_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_rq_pending_failure_eject_.inc(); + closed_with_active_rq_ = true; + break; + case StreamResetReason::LocalReset: + case StreamResetReason::ProtocolError: + case StreamResetReason::OverloadManager: +- parent_.host()->cluster().stats().upstream_rq_tx_reset_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_rq_tx_reset_.inc(); + break; + case StreamResetReason::RemoteReset: +- parent_.host()->cluster().stats().upstream_rq_rx_reset_.inc(); ++ parent_.host()->cluster().trafficStats().upstream_rq_rx_reset_.inc(); + break; + case StreamResetReason::LocalRefusedStreamReset: + case StreamResetReason::RemoteRefusedStreamReset: +diff --git a/source/common/http/conn_pool_base.h b/source/common/http/conn_pool_base.h +index 44451ab606..33e2faabf1 100644 +--- a/source/common/http/conn_pool_base.h ++++ b/source/common/http/conn_pool_base.h +@@ -129,11 +129,11 @@ public: + codec_client_ = parent.createCodecClient(data); + codec_client_->addConnectionCallbacks(*this); + codec_client_->setConnectionStats( +- {parent_.host()->cluster().stats().upstream_cx_rx_bytes_total_, +- parent_.host()->cluster().stats().upstream_cx_rx_bytes_buffered_, +- parent_.host()->cluster().stats().upstream_cx_tx_bytes_total_, +- parent_.host()->cluster().stats().upstream_cx_tx_bytes_buffered_, +- &parent_.host()->cluster().stats().bind_errors_, nullptr}); ++ {parent_.host()->cluster().trafficStats().upstream_cx_rx_bytes_total_, ++ parent_.host()->cluster().trafficStats().upstream_cx_rx_bytes_buffered_, ++ parent_.host()->cluster().trafficStats().upstream_cx_tx_bytes_total_, ++ parent_.host()->cluster().trafficStats().upstream_cx_tx_bytes_buffered_, ++ &parent_.host()->cluster().trafficStats().bind_errors_, nullptr}); + } +  + absl::optional protocol() const override { return codec_client_->protocol(); } +diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc +index 6c45e48eca..15db9536b9 100644 +--- a/source/common/http/conn_pool_grid.cc ++++ b/source/common/http/conn_pool_grid.cc +@@ -382,7 +382,7 @@ HttpServerPropertiesCache::Http3StatusTracker& ConnectivityGrid::getHttp3StatusT + bool ConnectivityGrid::isHttp3Broken() const { return getHttp3StatusTracker().isHttp3Broken(); } +  + void ConnectivityGrid::markHttp3Broken() { +- host_->cluster().stats().upstream_http3_broken_.inc(); ++ host_->cluster().trafficStats().upstream_http3_broken_.inc(); + getHttp3StatusTracker().markHttp3Broken(); + } +  +diff --git a/source/common/http/http1/balsa_parser.cc b/source/common/http/http1/balsa_parser.cc +index 4fe04ab5a3..4cba847421 100644 +--- a/source/common/http/http1/balsa_parser.cc ++++ b/source/common/http/http1/balsa_parser.cc +@@ -143,7 +143,12 @@ ParserStatus BalsaParser::getStatus() const { return status_; } + uint16_t BalsaParser::statusCode() const { return headers_.parsed_response_code(); } +  + bool BalsaParser::isHttp11() const { +- return absl::EndsWith(headers_.first_line(), Http::Headers::get().ProtocolStrings.Http11String); ++ if (framer_.is_request()) { ++ return absl::EndsWith(headers_.first_line(), Http::Headers::get().ProtocolStrings.Http11String); ++ } else { ++ return absl::StartsWith(headers_.first_line(), ++ Http::Headers::get().ProtocolStrings.Http11String); ++ } + } +  + absl::optional BalsaParser::contentLength() const { +diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc +index ab7165f5d3..77cf7ebcdd 100644 +--- a/source/common/http/http1/conn_pool.cc ++++ b/source/common/http/http1/conn_pool.cc +@@ -42,7 +42,7 @@ void ActiveClient::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, + close_connection_ = + HeaderUtility::shouldCloseConnection(parent_.codec_client_->protocol(), *headers); + if (close_connection_) { +- parent_.parent().host()->cluster().stats().upstream_cx_close_notify_.inc(); ++ parent_.parent().host()->cluster().trafficStats().upstream_cx_close_notify_.inc(); + } + ResponseDecoderWrapper::decodeHeaders(std::move(headers), end_stream); + } +@@ -76,7 +76,7 @@ ActiveClient::ActiveClient(HttpConnPoolImplBase& parent, + : Envoy::Http::ActiveClient(parent, parent.host()->cluster().maxRequestsPerConnection(), + /* effective_concurrent_stream_limit */ 1, + /* configured_concurrent_stream_limit */ 1, data) { +- parent.host()->cluster().stats().upstream_cx_http1_total_.inc(); ++ parent.host()->cluster().trafficStats().upstream_cx_http1_total_.inc(); + } +  + ActiveClient::~ActiveClient() { ASSERT(!stream_wrapper_.get()); } +diff --git a/source/common/http/http2/conn_pool.cc b/source/common/http/http2/conn_pool.cc +index 8879d75282..67761b3889 100644 +--- a/source/common/http/http2/conn_pool.cc ++++ b/source/common/http/http2/conn_pool.cc +@@ -45,7 +45,7 @@ ActiveClient::ActiveClient(HttpConnPoolImplBase& parent, + : MultiplexedActiveClientBase( + parent, calculateInitialStreamsLimit(parent.cache(), parent.origin(), parent.host()), + parent.host()->cluster().http2Options().max_concurrent_streams().value(), +- parent.host()->cluster().stats().upstream_cx_http2_total_, data) {} ++ parent.host()->cluster().trafficStats().upstream_cx_http2_total_, data) {} +  + ConnectionPool::InstancePtr + allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, +diff --git a/source/common/http/http3/conn_pool.cc b/source/common/http/http3/conn_pool.cc +index 25d3cc5bc5..061f71b364 100644 +--- a/source/common/http/http3/conn_pool.cc ++++ b/source/common/http/http3/conn_pool.cc +@@ -39,9 +39,9 @@ std::string sni(const Network::TransportSocketOptionsConstSharedPtr& options, +  + ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent, + Upstream::Host::CreateConnectionData& data) +- : MultiplexedActiveClientBase(parent, getMaxStreams(parent.host()->cluster()), +- getMaxStreams(parent.host()->cluster()), +- parent.host()->cluster().stats().upstream_cx_http3_total_, data), ++ : MultiplexedActiveClientBase( ++ parent, getMaxStreams(parent.host()->cluster()), getMaxStreams(parent.host()->cluster()), ++ parent.host()->cluster().trafficStats().upstream_cx_http3_total_, data), + async_connect_callback_(parent_.dispatcher().createSchedulableCallback([this]() { + if (state() != Envoy::ConnectionPool::ActiveClient::State::Connecting) { + return; +diff --git a/source/common/http/utility.cc b/source/common/http/utility.cc +index e5d8b851f8..b20bbeb086 100644 +--- a/source/common/http/utility.cc ++++ b/source/common/http/utility.cc +@@ -551,25 +551,6 @@ bool Utility::isWebSocketUpgradeRequest(const RequestHeaderMap& headers) { + Http::Headers::get().UpgradeValues.WebSocket)); + } +  +-void Utility::sendLocalReply(const bool& is_reset, StreamDecoderFilterCallbacks& callbacks, +- const LocalReplyData& local_reply_data) { +- absl::string_view details; +- if (callbacks.streamInfo().responseCodeDetails().has_value()) { +- details = callbacks.streamInfo().responseCodeDetails().value(); +- }; +- +- sendLocalReply( +- is_reset, +- Utility::EncodeFunctions{nullptr, nullptr, +- [&](ResponseHeaderMapPtr&& headers, bool end_stream) -> void { +- callbacks.encodeHeaders(std::move(headers), end_stream, details); +- }, +- [&](Buffer::Instance& data, bool end_stream) -> void { +- callbacks.encodeData(data, end_stream); +- }}, +- local_reply_data); +-} +- + void Utility::sendLocalReply(const bool& is_reset, const EncodeFunctions& encode_functions, + const LocalReplyData& local_reply_data) { + // encode_headers() may reset the stream, so the stream must not be reset before calling it. +diff --git a/source/common/http/utility.h b/source/common/http/utility.h +index 641bf2d764..90ae1fbb2f 100644 +--- a/source/common/http/utility.h ++++ b/source/common/http/utility.h +@@ -379,17 +379,6 @@ struct LocalReplyData { + bool is_head_request_ = false; + }; +  +-/** +- * Create a locally generated response using filter callbacks. +- * @param is_reset boolean reference that indicates whether a stream has been reset. It is the +- * responsibility of the caller to ensure that this is set to false if onDestroy() +- * is invoked in the context of sendLocalReply(). +- * @param callbacks supplies the filter callbacks to use. +- * @param local_reply_data struct which keeps data related to generate reply. +- */ +-void sendLocalReply(const bool& is_reset, StreamDecoderFilterCallbacks& callbacks, +- const LocalReplyData& local_reply_data); +- + /** + * Create a locally generated response using the provided lambdas. +  +diff --git a/source/common/matcher/matcher.h b/source/common/matcher/matcher.h +index 265182fc40..ff83ecfbdb 100644 +--- a/source/common/matcher/matcher.h ++++ b/source/common/matcher/matcher.h +@@ -24,8 +24,10 @@ + namespace Envoy { + namespace Matcher { +  +-template class ActionBase : public Action { ++template class ActionBase : public Base { + public: ++ template ActionBase(Args... args) : Base(args...) {} ++ + absl::string_view typeUrl() const override { return staticTypeUrl(); } +  + static absl::string_view staticTypeUrl() { +diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc +index 796fa59922..0c3da97b93 100644 +--- a/source/common/network/connection_impl.cc ++++ b/source/common/network/connection_impl.cc +@@ -811,9 +811,9 @@ void ConnectionImpl::dumpState(std::ostream& os, int indent_level) const { + ServerConnectionImpl::ServerConnectionImpl(Event::Dispatcher& dispatcher, + ConnectionSocketPtr&& socket, + TransportSocketPtr&& transport_socket, +- StreamInfo::StreamInfo& stream_info, bool connected) ++ StreamInfo::StreamInfo& stream_info) + : ConnectionImpl(dispatcher, std::move(socket), std::move(transport_socket), stream_info, +- connected) {} ++ true) {} +  + void ServerConnectionImpl::setTransportSocketConnectTimeout(std::chrono::milliseconds timeout, + Stats::Counter& timeout_stat) { +diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h +index bf9933f180..433112eedc 100644 +--- a/source/common/network/connection_impl.h ++++ b/source/common/network/connection_impl.h +@@ -238,8 +238,7 @@ private: + class ServerConnectionImpl : public ConnectionImpl, virtual public ServerConnection { + public: + ServerConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPtr&& socket, +- TransportSocketPtr&& transport_socket, StreamInfo::StreamInfo& stream_info, +- bool connected); ++ TransportSocketPtr&& transport_socket, StreamInfo::StreamInfo& stream_info); +  + // ServerConnection impl + void setTransportSocketConnectTimeout(std::chrono::milliseconds timeout, +diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc +index c920c9adfb..decd948fc9 100644 +--- a/source/common/router/config_impl.cc ++++ b/source/common/router/config_impl.cc +@@ -1880,9 +1880,7 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb + // The only possible action that can be used within the route matching context + // is the RouteMatchAction, so this must be true. + const auto result = match.result_(); +- ASSERT(result->typeUrl() == RouteMatchAction::staticTypeUrl()); +- ASSERT(dynamic_cast(result.get())); +- const RouteMatchAction& route_action = static_cast(*result); ++ const RouteMatchAction& route_action = result->getTyped(); +  + if (route_action.route()->matches(headers, stream_info, random_value)) { + return route_action.route(); +diff --git a/source/common/router/retry_state_impl.cc b/source/common/router/retry_state_impl.cc +index 419e398909..f4d3a868c7 100644 +--- a/source/common/router/retry_state_impl.cc ++++ b/source/common/router/retry_state_impl.cc +@@ -185,13 +185,13 @@ void RetryStateImpl::enableBackoffTimer() { + // be reused. + ratelimited_backoff_strategy_.reset(); +  +- cluster_.stats().upstream_rq_retry_backoff_ratelimited_.inc(); ++ cluster_.trafficStats().upstream_rq_retry_backoff_ratelimited_.inc(); +  + } else { + // Otherwise we use a fully jittered exponential backoff algorithm. + retry_timer_->enableTimer(std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); +  +- cluster_.stats().upstream_rq_retry_backoff_exponential_.inc(); ++ cluster_.trafficStats().upstream_rq_retry_backoff_exponential_.inc(); + } + } +  +@@ -277,7 +277,7 @@ RetryStatus RetryStateImpl::shouldRetry(RetryDecision would_retry, DoRetryCallba + // retry this particular request, we can infer that we did a retry earlier + // and it was successful. + if ((backoff_callback_ || next_loop_callback_) && would_retry == RetryDecision::NoRetry) { +- cluster_.stats().upstream_rq_retry_success_.inc(); ++ cluster_.trafficStats().upstream_rq_retry_success_.inc(); + if (vcluster_) { + vcluster_->stats().upstream_rq_retry_success_.inc(); + } +@@ -295,7 +295,7 @@ RetryStatus RetryStateImpl::shouldRetry(RetryDecision would_retry, DoRetryCallba + // The request has exhausted the number of retries allotted to it by the retry policy configured + // (or the x-envoy-max-retries header). + if (retries_remaining_ == 0) { +- cluster_.stats().upstream_rq_retry_limit_exceeded_.inc(); ++ cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.inc(); + if (vcluster_) { + vcluster_->stats().upstream_rq_retry_limit_exceeded_.inc(); + } +@@ -308,7 +308,7 @@ RetryStatus RetryStateImpl::shouldRetry(RetryDecision would_retry, DoRetryCallba + retries_remaining_--; +  + if (!cluster_.resourceManager(priority_).retries().canCreate()) { +- cluster_.stats().upstream_rq_retry_overflow_.inc(); ++ cluster_.trafficStats().upstream_rq_retry_overflow_.inc(); + if (vcluster_) { + vcluster_->stats().upstream_rq_retry_overflow_.inc(); + } +@@ -324,7 +324,7 @@ RetryStatus RetryStateImpl::shouldRetry(RetryDecision would_retry, DoRetryCallba +  + ASSERT(!backoff_callback_ && !next_loop_callback_); + cluster_.resourceManager(priority_).retries().inc(); +- cluster_.stats().upstream_rq_retry_.inc(); ++ cluster_.trafficStats().upstream_rq_retry_.inc(); + if (vcluster_) { + vcluster_->stats().upstream_rq_retry_.inc(); + } +diff --git a/source/common/router/router.cc b/source/common/router/router.cc +index acd45f3147..40a377c7e6 100644 +--- a/source/common/router/router.cc ++++ b/source/common/router/router.cc +@@ -509,7 +509,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, + modify_headers(headers); + }, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().MaintenanceMode); +- cluster_->stats().upstream_rq_maintenance_mode_.inc(); ++ cluster_->trafficStats().upstream_rq_maintenance_mode_.inc(); + return Http::FilterHeadersStatus::StopIteration; + } +  +@@ -754,7 +754,7 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea + "The request payload has at least {} bytes data which exceeds buffer limit {}. Give " + "up on the retry/shadow.", + getLength(callbacks_->decodingBuffer()) + data.length(), retry_shadow_buffer_limit_); +- cluster_->stats().retry_or_shadow_abandoned_.inc(); ++ cluster_->trafficStats().retry_or_shadow_abandoned_.inc(); + retry_state_.reset(); + buffering = false; + active_shadow_policies_.clear(); +@@ -956,7 +956,7 @@ void Filter::onResponseTimeout() { + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.do_not_await_headers_on_upstream_timeout_to_emit_stats") || + upstream_request->awaitingHeaders()) { +- cluster_->stats().upstream_rq_timeout_.inc(); ++ cluster_->trafficStats().upstream_rq_timeout_.inc(); + if (request_vcluster_) { + request_vcluster_->stats().upstream_rq_timeout_.inc(); + } +@@ -1030,12 +1030,13 @@ void Filter::onSoftPerTryTimeout(UpstreamRequest& upstream_request) { + } +  + void Filter::onPerTryIdleTimeout(UpstreamRequest& upstream_request) { +- onPerTryTimeoutCommon(upstream_request, cluster_->stats().upstream_rq_per_try_idle_timeout_, ++ onPerTryTimeoutCommon(upstream_request, ++ cluster_->trafficStats().upstream_rq_per_try_idle_timeout_, + StreamInfo::ResponseCodeDetails::get().UpstreamPerTryIdleTimeout); + } +  + void Filter::onPerTryTimeout(UpstreamRequest& upstream_request) { +- onPerTryTimeoutCommon(upstream_request, cluster_->stats().upstream_rq_per_try_timeout_, ++ onPerTryTimeoutCommon(upstream_request, cluster_->trafficStats().upstream_rq_per_try_timeout_, + StreamInfo::ResponseCodeDetails::get().UpstreamPerTryTimeout); + } +  +@@ -1624,7 +1625,7 @@ bool Filter::setupRedirect(const Http::ResponseHeaderMap& headers) { + convertRequestHeadersForInternalRedirect(*downstream_headers_, *location, status_code) && + callbacks_->recreateStream(&headers)) { + ENVOY_STREAM_LOG(debug, "Internal redirect succeeded", *callbacks_); +- cluster_->stats().upstream_internal_redirect_succeeded_total_.inc(); ++ cluster_->trafficStats().upstream_internal_redirect_succeeded_total_.inc(); + return true; + } + // convertRequestHeadersForInternalRedirect logs failure reasons but log +@@ -1637,7 +1638,7 @@ bool Filter::setupRedirect(const Http::ResponseHeaderMap& headers) { + ENVOY_STREAM_LOG(trace, "Internal redirect failed: missing location header", *callbacks_); + } +  +- cluster_->stats().upstream_internal_redirect_failed_total_.inc(); ++ cluster_->trafficStats().upstream_internal_redirect_failed_total_.inc(); + return false; + } +  +diff --git a/source/common/router/upstream_codec_filter.cc b/source/common/router/upstream_codec_filter.cc +index 07e43f4998..33994cbdce 100644 +--- a/source/common/router/upstream_codec_filter.cc ++++ b/source/common/router/upstream_codec_filter.cc +@@ -28,12 +28,12 @@ namespace Envoy { + namespace Router { +  + void UpstreamCodecFilter::onBelowWriteBufferLowWatermark() { +- callbacks_->clusterInfo()->stats().upstream_flow_control_resumed_reading_total_.inc(); ++ callbacks_->clusterInfo()->trafficStats().upstream_flow_control_resumed_reading_total_.inc(); + callbacks_->upstreamCallbacks()->upstream()->readDisable(false); + } +  + void UpstreamCodecFilter::onAboveWriteBufferHighWatermark() { +- callbacks_->clusterInfo()->stats().upstream_flow_control_paused_reading_total_.inc(); ++ callbacks_->clusterInfo()->trafficStats().upstream_flow_control_paused_reading_total_.inc(); + callbacks_->upstreamCallbacks()->upstream()->readDisable(true); + } +  +diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc +index 5407c2ce1d..cb59213804 100644 +--- a/source/common/router/upstream_request.cc ++++ b/source/common/router/upstream_request.cc +@@ -197,7 +197,7 @@ void UpstreamRequest::cleanUp() { +  + while (downstream_data_disabled_ != 0) { + parent_.callbacks()->onDecoderFilterBelowWriteBufferLowWatermark(); +- parent_.cluster()->stats().upstream_flow_control_drained_total_.inc(); ++ parent_.cluster()->trafficStats().upstream_flow_control_drained_total_.inc(); + --downstream_data_disabled_; + } + if (allow_upstream_filters_) { +@@ -753,7 +753,7 @@ UpstreamToDownstream& UpstreamRequest::upstreamToDownstream() { + } +  + void UpstreamRequest::onStreamMaxDurationReached() { +- upstream_host_->cluster().stats().upstream_rq_max_duration_reached_.inc(); ++ upstream_host_->cluster().trafficStats().upstream_rq_max_duration_reached_.inc(); +  + // The upstream had closed then try to retry along with retry policy. + parent_.onStreamMaxDurationReached(*this); +@@ -780,7 +780,7 @@ void UpstreamRequest::DownstreamWatermarkManager::onAboveWriteBufferHighWatermar + // The downstream connection is overrun. Pause reads from upstream. + // If there are multiple calls to readDisable either the codec (H2) or the underlying + // Network::Connection (H1) will handle reference counting. +- parent_.parent_.cluster()->stats().upstream_flow_control_paused_reading_total_.inc(); ++ parent_.parent_.cluster()->trafficStats().upstream_flow_control_paused_reading_total_.inc(); + parent_.upstream_->readDisable(true); + } +  +@@ -789,7 +789,7 @@ void UpstreamRequest::DownstreamWatermarkManager::onBelowWriteBufferLowWatermark +  + // One source of connection blockage has buffer available. Pass this on to the stream, which + // will resume reads if this was the last remaining high watermark. +- parent_.parent_.cluster()->stats().upstream_flow_control_resumed_reading_total_.inc(); ++ parent_.parent_.cluster()->trafficStats().upstream_flow_control_resumed_reading_total_.inc(); + parent_.upstream_->readDisable(false); + } +  +@@ -804,7 +804,7 @@ void UpstreamRequest::disableDataFromDownstreamForFlowControl() { + // the per try timeout timer is started only after downstream_end_stream_ + // is true. + ASSERT(parent_.upstreamRequests().size() == 1 || parent_.downstreamEndStream()); +- parent_.cluster()->stats().upstream_flow_control_backed_up_total_.inc(); ++ parent_.cluster()->trafficStats().upstream_flow_control_backed_up_total_.inc(); + parent_.callbacks()->onDecoderFilterAboveWriteBufferHighWatermark(); + ++downstream_data_disabled_; + } +@@ -820,7 +820,7 @@ void UpstreamRequest::enableDataFromDownstreamForFlowControl() { + // the per try timeout timer is started only after downstream_end_stream_ + // is true. + ASSERT(parent_.upstreamRequests().size() == 1 || parent_.downstreamEndStream()); +- parent_.cluster()->stats().upstream_flow_control_drained_total_.inc(); ++ parent_.cluster()->trafficStats().upstream_flow_control_drained_total_.inc(); + parent_.callbacks()->onDecoderFilterBelowWriteBufferLowWatermark(); + ASSERT(downstream_data_disabled_ != 0); + if (downstream_data_disabled_ > 0) { +diff --git a/source/common/tcp/conn_pool.cc b/source/common/tcp/conn_pool.cc +index 0ece4a46b1..036315cc02 100644 +--- a/source/common/tcp/conn_pool.cc ++++ b/source/common/tcp/conn_pool.cc +@@ -25,11 +25,11 @@ ActiveTcpClient::ActiveTcpClient(Envoy::ConnectionPool::ConnPoolImplBase& parent + connection_->addConnectionCallbacks(*this); + read_filter_handle_ = std::make_shared(*this); + connection_->addReadFilter(read_filter_handle_); +- connection_->setConnectionStats({host->cluster().stats().upstream_cx_rx_bytes_total_, +- host->cluster().stats().upstream_cx_rx_bytes_buffered_, +- host->cluster().stats().upstream_cx_tx_bytes_total_, +- host->cluster().stats().upstream_cx_tx_bytes_buffered_, +- &host->cluster().stats().bind_errors_, nullptr}); ++ connection_->setConnectionStats({host->cluster().trafficStats().upstream_cx_rx_bytes_total_, ++ host->cluster().trafficStats().upstream_cx_rx_bytes_buffered_, ++ host->cluster().trafficStats().upstream_cx_tx_bytes_total_, ++ host->cluster().trafficStats().upstream_cx_tx_bytes_buffered_, ++ &host->cluster().trafficStats().bind_errors_, nullptr}); + connection_->noDelay(true); + connection_->connect(); + } +diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc +index aed1680c88..473270115e 100644 +--- a/source/common/tcp_proxy/tcp_proxy.cc ++++ b/source/common/tcp_proxy/tcp_proxy.cc +@@ -234,12 +234,12 @@ void Filter::readDisableUpstream(bool disable) { + if (disable) { + read_callbacks_->upstreamHost() + ->cluster() +- .stats() ++ .trafficStats() + .upstream_flow_control_paused_reading_total_.inc(); + } else { + read_callbacks_->upstreamHost() + ->cluster() +- .stats() ++ .trafficStats() + .upstream_flow_control_resumed_reading_total_.inc(); + } + } +@@ -377,7 +377,7 @@ Network::FilterStatus Filter::establishUpstreamConnection() { + // will never be released. + if (!cluster->resourceManager(Upstream::ResourcePriority::Default).connections().canCreate()) { + getStreamInfo().setResponseFlag(StreamInfo::ResponseFlag::UpstreamOverflow); +- cluster->stats().upstream_cx_overflow_.inc(); ++ cluster->trafficStats().upstream_cx_overflow_.inc(); + onInitFailure(UpstreamFailureReason::ResourceLimitExceeded); + return Network::FilterStatus::StopIteration; + } +@@ -385,7 +385,7 @@ Network::FilterStatus Filter::establishUpstreamConnection() { + const uint32_t max_connect_attempts = config_->maxConnectAttempts(); + if (connect_attempts_ >= max_connect_attempts) { + getStreamInfo().setResponseFlag(StreamInfo::ResponseFlag::UpstreamRetryLimitExceeded); +- cluster->stats().upstream_cx_connect_attempts_exceeded_.inc(); ++ cluster->trafficStats().upstream_cx_connect_attempts_exceeded_.inc(); + onInitFailure(UpstreamFailureReason::ConnectFailed); + return Network::FilterStatus::StopIteration; + } +@@ -417,7 +417,7 @@ Network::FilterStatus Filter::establishUpstreamConnection() { +  + if (!maybeTunnel(*thread_local_cluster)) { + // Either cluster is unknown or there are no healthy hosts. tcpConnPool() increments +- // cluster->stats().upstream_cx_none_healthy in the latter case. ++ // cluster->trafficStats().upstream_cx_none_healthy in the latter case. + getStreamInfo().setResponseFlag(StreamInfo::ResponseFlag::NoHealthyUpstream); + onInitFailure(UpstreamFailureReason::NoHealthyUpstream); + } +diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc +index 6aeef2866c..b591852843 100644 +--- a/source/common/upstream/cluster_manager_impl.cc ++++ b/source/common/upstream/cluster_manager_impl.cc +@@ -295,6 +295,9 @@ ClusterManagerImpl::ClusterManagerImpl( + time_source_(main_thread_dispatcher.timeSource()), dispatcher_(main_thread_dispatcher), + http_context_(http_context), router_context_(router_context), + cluster_stat_names_(stats.symbolTable()), ++ cluster_config_update_stat_names_(stats.symbolTable()), ++ cluster_lb_stat_names_(stats.symbolTable()), ++ cluster_endpoint_stat_names_(stats.symbolTable()), + cluster_load_report_stat_names_(stats.symbolTable()), + cluster_circuit_breakers_stat_names_(stats.symbolTable()), + cluster_request_response_size_stat_names_(stats.symbolTable()), +@@ -885,14 +888,14 @@ ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& clust + if (cluster_reference.info()->lbType() == LoadBalancerType::RingHash) { + if (!cluster_reference.info()->lbSubsetInfo().isEnabled()) { + cluster_entry_it->second->thread_aware_lb_ = std::make_unique( +- cluster_reference.prioritySet(), cluster_reference.info()->stats(), ++ cluster_reference.prioritySet(), cluster_reference.info()->lbStats(), + cluster_reference.info()->statsScope(), runtime_, random_, + cluster_reference.info()->lbRingHashConfig(), cluster_reference.info()->lbConfig()); + } + } else if (cluster_reference.info()->lbType() == LoadBalancerType::Maglev) { + if (!cluster_reference.info()->lbSubsetInfo().isEnabled()) { + cluster_entry_it->second->thread_aware_lb_ = std::make_unique( +- cluster_reference.prioritySet(), cluster_reference.info()->stats(), ++ cluster_reference.prioritySet(), cluster_reference.info()->lbStats(), + cluster_reference.info()->statsScope(), runtime_, random_, + cluster_reference.info()->lbMaglevConfig(), cluster_reference.info()->lbConfig()); + } +@@ -1151,7 +1154,7 @@ Host::CreateConnectionData ClusterManagerImpl::ThreadLocalClusterManagerImpl::Cl + } + return conn_info; + } else { +- cluster_info_->stats().upstream_cx_none_healthy_.inc(); ++ cluster_info_->trafficStats().upstream_cx_none_healthy_.inc(); + return {nullptr, nullptr}; + } + } +@@ -1516,7 +1519,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::ClusterEntry( + // benefit given the healthy panic, locality, and priority calculations that take place. + if (cluster->lbSubsetInfo().isEnabled()) { + lb_ = std::make_unique( +- cluster->lbType(), priority_set_, parent_.local_priority_set_, cluster->stats(), ++ cluster->lbType(), priority_set_, parent_.local_priority_set_, cluster->lbStats(), + cluster->statsScope(), parent.parent_.runtime_, parent.parent_.random_, + cluster->lbSubsetInfo(), cluster->lbRingHashConfig(), cluster->lbMaglevConfig(), + cluster->lbRoundRobinConfig(), cluster->lbLeastRequestConfig(), cluster->lbConfig(), +@@ -1526,7 +1529,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::ClusterEntry( + case LoadBalancerType::LeastRequest: { + ASSERT(lb_factory_ == nullptr); + lb_ = std::make_unique( +- priority_set_, parent_.local_priority_set_, cluster->stats(), parent.parent_.runtime_, ++ priority_set_, parent_.local_priority_set_, cluster->lbStats(), parent.parent_.runtime_, + parent.parent_.random_, cluster->lbConfig(), cluster->lbLeastRequestConfig(), + parent.thread_local_dispatcher_.timeSource()); + break; +@@ -1534,14 +1537,14 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::ClusterEntry( + case LoadBalancerType::Random: { + ASSERT(lb_factory_ == nullptr); + lb_ = std::make_unique(priority_set_, parent_.local_priority_set_, +- cluster->stats(), parent.parent_.runtime_, ++ cluster->lbStats(), parent.parent_.runtime_, + parent.parent_.random_, cluster->lbConfig()); + break; + } + case LoadBalancerType::RoundRobin: { + ASSERT(lb_factory_ == nullptr); + lb_ = std::make_unique( +- priority_set_, parent_.local_priority_set_, cluster->stats(), parent.parent_.runtime_, ++ priority_set_, parent_.local_priority_set_, cluster->lbStats(), parent.parent_.runtime_, + parent.parent_.random_, cluster->lbConfig(), cluster->lbRoundRobinConfig(), + parent.thread_local_dispatcher_.timeSource()); + break; +@@ -1623,7 +1626,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::httpConnPoolImp + if (!host) { + if (!peek) { + ENVOY_LOG(debug, "no healthy host for HTTP connection pool"); +- cluster_info_->stats().upstream_cx_none_healthy_.inc(); ++ cluster_info_->trafficStats().upstream_cx_none_healthy_.inc(); + } + return nullptr; + } +@@ -1753,7 +1756,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::tcpConnPoolImpl + if (!host) { + if (!peek) { + ENVOY_LOG(debug, "no healthy host for TCP connection pool"); +- cluster_info_->stats().upstream_cx_none_healthy_.inc(); ++ cluster_info_->trafficStats().upstream_cx_none_healthy_.inc(); + } + return nullptr; + } +diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h +index 77ccfa324b..b28c92f306 100644 +--- a/source/common/upstream/cluster_manager_impl.h ++++ b/source/common/upstream/cluster_manager_impl.h +@@ -328,7 +328,14 @@ public: + void + initializeSecondaryClusters(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) override; +  +- const ClusterStatNames& clusterStatNames() const override { return cluster_stat_names_; } ++ const ClusterTrafficStatNames& clusterStatNames() const override { return cluster_stat_names_; } ++ const ClusterConfigUpdateStatNames& clusterConfigUpdateStatNames() const override { ++ return cluster_config_update_stat_names_; ++ } ++ const ClusterLbStatNames& clusterLbStatNames() const override { return cluster_lb_stat_names_; } ++ const ClusterEndpointStatNames& clusterEndpointStatNames() const override { ++ return cluster_endpoint_stat_names_; ++ } + const ClusterLoadReportStatNames& clusterLoadReportStatNames() const override { + return cluster_load_report_stat_names_; + } +@@ -781,7 +788,10 @@ private: + Event::Dispatcher& dispatcher_; + Http::Context& http_context_; + Router::Context& router_context_; +- ClusterStatNames cluster_stat_names_; ++ ClusterTrafficStatNames cluster_stat_names_; ++ ClusterConfigUpdateStatNames cluster_config_update_stat_names_; ++ ClusterLbStatNames cluster_lb_stat_names_; ++ ClusterEndpointStatNames cluster_endpoint_stat_names_; + ClusterLoadReportStatNames cluster_load_report_stat_names_; + ClusterCircuitBreakersStatNames cluster_circuit_breakers_stat_names_; + ClusterRequestResponseSizeStatNames cluster_request_response_size_stat_names_; +diff --git a/source/common/upstream/conn_pool_map_impl.h b/source/common/upstream/conn_pool_map_impl.h +index 1eebd6ec82..e30a73bfd2 100644 +--- a/source/common/upstream/conn_pool_map_impl.h ++++ b/source/common/upstream/conn_pool_map_impl.h +@@ -35,7 +35,7 @@ ConnPoolMap::getPool(const KEY_TYPE& key, const PoolFactory + if (!connPoolResource.canCreate()) { + // We're full. Try to free up a pool. If we can't, bail out. + if (!freeOnePool()) { +- host_->cluster().stats().upstream_cx_pool_overflow_.inc(); ++ host_->cluster().trafficStats().upstream_cx_pool_overflow_.inc(); + return absl::nullopt; + } +  +diff --git a/source/common/upstream/eds.cc b/source/common/upstream/eds.cc +index dda70e1f37..b8dd5ec5b2 100644 +--- a/source/common/upstream/eds.cc ++++ b/source/common/upstream/eds.cc +@@ -120,7 +120,7 @@ void EdsClusterImpl::BatchUpdateHelper::batchUpdate(PrioritySet::HostUpdateCb& h + } +  + if (!cluster_rebuilt) { +- parent_.info_->stats().update_no_rebuild_.inc(); ++ parent_.info_->configUpdateStats().update_no_rebuild_.inc(); + } +  + // If we didn't setup to initialize when our first round of health checking is complete, just +@@ -178,7 +178,7 @@ void EdsClusterImpl::onConfigUpdate(const std::vector 0) { + // Stat to track how often we receive valid assignment_timeout in response. +- info_->stats().assignment_timeout_received_.inc(); ++ info_->configUpdateStats().assignment_timeout_received_.inc(); + assignment_timeout_->enableTimer(std::chrono::milliseconds(stale_after_ms)); + } +  +@@ -260,7 +260,7 @@ void EdsClusterImpl::onConfigUpdate(const std::vectorstats().update_empty_.inc(); ++ info_->configUpdateStats().update_empty_.inc(); + onPreInitComplete(); + return false; + } +@@ -286,7 +286,7 @@ void EdsClusterImpl::onAssignmentTimeout() { + std::vector resource_refs = {*decoded_resource}; + onConfigUpdate(resource_refs, ""); + // Stat to track how often we end up with stale assignments. +- info_->stats().assignment_stale_.inc(); ++ info_->configUpdateStats().assignment_stale_.inc(); + } +  + void EdsClusterImpl::reloadHealthyHostsHelper(const HostSharedPtr& host) { +diff --git a/source/common/upstream/health_checker_base_impl.cc b/source/common/upstream/health_checker_base_impl.cc +index da5963e194..4d73d67f9c 100644 +--- a/source/common/upstream/health_checker_base_impl.cc ++++ b/source/common/upstream/health_checker_base_impl.cc +@@ -105,7 +105,7 @@ std::chrono::milliseconds HealthCheckerImplBase::interval(HealthState state, + // If a connection has been established, we choose an interval based on the host's health. Please + // refer to the HealthCheck API documentation for more details. + uint64_t base_time_ms; +- if (cluster_.info()->stats().upstream_cx_total_.used()) { ++ if (cluster_.info()->trafficStats().upstream_cx_total_.used()) { + // When healthy/unhealthy threshold is configured the health transition of a host will be + // delayed. In this situation Envoy should use the edge interval settings between health checks. + // +@@ -160,6 +160,9 @@ HealthCheckerImplBase::intervalWithJitter(uint64_t base_time_ms, +  + void HealthCheckerImplBase::addHosts(const HostVector& hosts) { + for (const HostSharedPtr& host : hosts) { ++ if (host->disableActiveHealthCheck()) { ++ continue; ++ } + active_sessions_[host] = makeSession(host); + host->setHealthChecker( + HealthCheckHostMonitorPtr{new HealthCheckHostMonitorImpl(shared_from_this(), host)}); +@@ -171,6 +174,9 @@ void HealthCheckerImplBase::onClusterMemberUpdate(const HostVector& hosts_added, + const HostVector& hosts_removed) { + addHosts(hosts_added); + for (const HostSharedPtr& host : hosts_removed) { ++ if (host->disableActiveHealthCheck()) { ++ continue; ++ } + auto session_iter = active_sessions_.find(host); + ASSERT(active_sessions_.end() != session_iter); + // This deletion can happen inline in response to a host failure, so we deferred delete. +diff --git a/source/common/upstream/health_discovery_service.cc b/source/common/upstream/health_discovery_service.cc +index 101631cc23..818126c78c 100644 +--- a/source/common/upstream/health_discovery_service.cc ++++ b/source/common/upstream/health_discovery_service.cc +@@ -167,6 +167,12 @@ envoy::config::cluster::v3::Cluster HdsDelegate::createClusterConfig( +  + // add all endpoints for this locality group to the config + for (const auto& endpoint : locality_endpoints.endpoints()) { ++ if (endpoint.has_health_check_config() && ++ endpoint.health_check_config().disable_active_health_check()) { ++ ENVOY_LOG(debug, "Skip adding the endpoint {} with optional disabled health check for HDS.", ++ endpoint.DebugString()); ++ continue; ++ } + auto* new_endpoint = endpoints->add_lb_endpoints()->mutable_endpoint(); + new_endpoint->mutable_address()->MergeFrom(endpoint.address()); + new_endpoint->mutable_health_check_config()->MergeFrom(endpoint.health_check_config()); +diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc +index 3cd2987a9e..2ed3728267 100644 +--- a/source/common/upstream/load_balancer_impl.cc ++++ b/source/common/upstream/load_balancer_impl.cc +@@ -109,7 +109,7 @@ LoadBalancerBase::choosePriority(uint64_t hash, const HealthyLoad& healthy_per_p + } +  + LoadBalancerBase::LoadBalancerBase( +- const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, ++ const PrioritySet& priority_set, ClusterLbStats& stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : stats_(stats), runtime_(runtime), random_(random), +@@ -351,7 +351,7 @@ LoadBalancerBase::chooseHostSet(LoadBalancerContext* context, uint64_t hash) con + } +  + ZoneAwareLoadBalancerBase::ZoneAwareLoadBalancerBase( +- const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, ++ const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : LoadBalancerBase(priority_set, stats, runtime, random, common_config), +@@ -717,7 +717,7 @@ const HostVector& ZoneAwareLoadBalancerBase::hostSourceToHosts(HostsSource hosts + } +  + EdfLoadBalancerBase::EdfLoadBalancerBase( +- const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, ++ const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config, + const absl::optional slow_start_config, +diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h +index c513ccefae..a8483408ec 100644 +--- a/source/common/upstream/load_balancer_impl.h ++++ b/source/common/upstream/load_balancer_impl.h +@@ -68,7 +68,7 @@ protected: + */ + void recalculateLoadInTotalPanic(); +  +- LoadBalancerBase(const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, ++ LoadBalancerBase(const PrioritySet& priority_set, ClusterLbStats& stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); +  +@@ -103,7 +103,7 @@ protected: + } + } +  +- ClusterStats& stats_; ++ ClusterLbStats& stats_; + Runtime::Loader& runtime_; + std::deque stashed_random_; + Random::RandomGenerator& random_; +@@ -199,7 +199,7 @@ public: + protected: + // Both priority_set and local_priority_set if non-null must have at least one host set. + ZoneAwareLoadBalancerBase( +- const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, ++ const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); +  +@@ -401,7 +401,7 @@ class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase, + Logger::Loggable { + public: + EdfLoadBalancerBase( +- const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, ++ const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config, + const absl::optional slow_start_cofig, +@@ -469,7 +469,7 @@ protected: + class RoundRobinLoadBalancer : public EdfLoadBalancerBase { + public: + RoundRobinLoadBalancer( +- const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, ++ const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config, + const absl::optional +@@ -547,7 +547,7 @@ private: + class LeastRequestLoadBalancer : public EdfLoadBalancerBase { + public: + LeastRequestLoadBalancer( +- const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, ++ const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterLbStats& stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config, + const absl::optional +@@ -647,7 +647,8 @@ class RandomLoadBalancer : public ZoneAwareLoadBalancerBase, + Logger::Loggable { + public: + RandomLoadBalancer(const PrioritySet& priority_set, const PrioritySet* local_priority_set, +- ClusterStats& stats, Runtime::Loader& runtime, Random::RandomGenerator& random, ++ ClusterLbStats& stats, Runtime::Loader& runtime, ++ Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : ZoneAwareLoadBalancerBase(priority_set, local_priority_set, stats, runtime, random, + common_config) {} +diff --git a/source/common/upstream/maglev_lb.cc b/source/common/upstream/maglev_lb.cc +index 7617853725..1f33f094a2 100644 +--- a/source/common/upstream/maglev_lb.cc ++++ b/source/common/upstream/maglev_lb.cc +@@ -93,7 +93,7 @@ uint64_t MaglevTable::permutation(const TableBuildEntry& entry) { + } +  + MaglevLoadBalancer::MaglevLoadBalancer( +- const PrioritySet& priority_set, ClusterStats& stats, Stats::Scope& scope, ++ const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const absl::optional& config, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +diff --git a/source/common/upstream/maglev_lb.h b/source/common/upstream/maglev_lb.h +index 0d1c6fc5f7..3f0d4d5b55 100644 +--- a/source/common/upstream/maglev_lb.h ++++ b/source/common/upstream/maglev_lb.h +@@ -72,7 +72,7 @@ class MaglevLoadBalancer : public ThreadAwareLoadBalancerBase, + Logger::Loggable { + public: + MaglevLoadBalancer( +- const PrioritySet& priority_set, ClusterStats& stats, Stats::Scope& scope, ++ const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const absl::optional& config, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); +diff --git a/source/common/upstream/ring_hash_lb.cc b/source/common/upstream/ring_hash_lb.cc +index f20ebd676b..7548e0d5f1 100644 +--- a/source/common/upstream/ring_hash_lb.cc ++++ b/source/common/upstream/ring_hash_lb.cc +@@ -17,7 +17,7 @@ namespace Envoy { + namespace Upstream { +  + RingHashLoadBalancer::RingHashLoadBalancer( +- const PrioritySet& priority_set, ClusterStats& stats, Stats::Scope& scope, ++ const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const absl::optional& config, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +diff --git a/source/common/upstream/ring_hash_lb.h b/source/common/upstream/ring_hash_lb.h +index aa861eb9fb..2ad0fe263d 100644 +--- a/source/common/upstream/ring_hash_lb.h ++++ b/source/common/upstream/ring_hash_lb.h +@@ -41,7 +41,7 @@ class RingHashLoadBalancer : public ThreadAwareLoadBalancerBase, + Logger::Loggable { + public: + RingHashLoadBalancer( +- const PrioritySet& priority_set, ClusterStats& stats, Stats::Scope& scope, ++ const PrioritySet& priority_set, ClusterLbStats& stats, Stats::Scope& scope, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const absl::optional& config, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); +diff --git a/source/common/upstream/subset_lb.cc b/source/common/upstream/subset_lb.cc +index 4474be84c0..bd7eaa8802 100644 +--- a/source/common/upstream/subset_lb.cc ++++ b/source/common/upstream/subset_lb.cc +@@ -23,7 +23,7 @@ using HostPredicate = std::function; +  + SubsetLoadBalancer::SubsetLoadBalancer( + LoadBalancerType lb_type, PrioritySet& priority_set, const PrioritySet* local_priority_set, +- ClusterStats& stats, Stats::Scope& scope, Runtime::Loader& runtime, ++ ClusterLbStats& stats, Stats::Scope& scope, Runtime::Loader& runtime, + Random::RandomGenerator& random, const LoadBalancerSubsetInfo& subsets, + const absl::optional& + lb_ring_hash_config, +@@ -466,9 +466,9 @@ void SubsetLoadBalancer::processSubsets(uint32_t priority, const HostVector& all + } + } +  +- // This stat isn't added to `ClusterStats` because it wouldn't be used for nearly all clusters, +- // and is only set during configuration updates, not in the data path, so performance of looking +- // up the stat isn't critical. ++ // This stat isn't added to `ClusterTrafficStats` because it wouldn't be used for nearly all ++ // clusters, and is only set during configuration updates, not in the data path, so performance of ++ // looking up the stat isn't critical. + if (single_duplicate_stat_ == nullptr) { + Stats::StatNameManagedStorage name_storage("lb_subsets_single_host_per_subset_duplicate", + scope_.symbolTable()); +diff --git a/source/common/upstream/subset_lb.h b/source/common/upstream/subset_lb.h +index 091c15718d..bf02b1ee64 100644 +--- a/source/common/upstream/subset_lb.h ++++ b/source/common/upstream/subset_lb.h +@@ -30,7 +30,7 @@ class SubsetLoadBalancer : public LoadBalancer, Logger::Loggable& + lb_ring_hash_config, +@@ -365,7 +365,7 @@ private: + const absl::optional + least_request_config_; + const envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; +- ClusterStats& stats_; ++ ClusterLbStats& stats_; + Stats::Scope& scope_; + Runtime::Loader& runtime_; + Random::RandomGenerator& random_; +diff --git a/source/common/upstream/thread_aware_lb_impl.cc b/source/common/upstream/thread_aware_lb_impl.cc +index 047ee00518..17c83d315b 100644 +--- a/source/common/upstream/thread_aware_lb_impl.cc ++++ b/source/common/upstream/thread_aware_lb_impl.cc +@@ -190,7 +190,7 @@ double ThreadAwareLoadBalancerBase::BoundedLoadHashingLoadBalancer::hostOverload + // TODO(scheler): This will not work if rq_active cluster stat is disabled, need to detect + // and alert the user if that's the case. +  +- const uint32_t overall_active = host.cluster().stats().upstream_rq_active_.value(); ++ const uint32_t overall_active = host.cluster().trafficStats().upstream_rq_active_.value(); + const uint32_t host_active = host.stats().rq_active_.value(); +  + const uint32_t total_slots = ((overall_active + 1) * hash_balance_factor_ + 99) / 100; +diff --git a/source/common/upstream/thread_aware_lb_impl.h b/source/common/upstream/thread_aware_lb_impl.h +index 81e9d5212d..2cd2b78a17 100644 +--- a/source/common/upstream/thread_aware_lb_impl.h ++++ b/source/common/upstream/thread_aware_lb_impl.h +@@ -107,7 +107,7 @@ public: +  + protected: + ThreadAwareLoadBalancerBase( +- const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, ++ const PrioritySet& priority_set, ClusterLbStats& stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : LoadBalancerBase(priority_set, stats, runtime, random, common_config), +@@ -121,7 +121,7 @@ private: + using PerPriorityStatePtr = std::unique_ptr; +  + struct LoadBalancerImpl : public LoadBalancer { +- LoadBalancerImpl(ClusterStats& stats, Random::RandomGenerator& random) ++ LoadBalancerImpl(ClusterLbStats& stats, Random::RandomGenerator& random) + : stats_(stats), random_(random) {} +  + // Upstream::LoadBalancer +@@ -138,7 +138,7 @@ private: + return {}; + } +  +- ClusterStats& stats_; ++ ClusterLbStats& stats_; + Random::RandomGenerator& random_; + std::shared_ptr> per_priority_state_; + std::shared_ptr healthy_per_priority_load_; +@@ -146,7 +146,7 @@ private: + }; +  + struct LoadBalancerFactoryImpl : public LoadBalancerFactory { +- LoadBalancerFactoryImpl(ClusterStats& stats, Random::RandomGenerator& random) ++ LoadBalancerFactoryImpl(ClusterLbStats& stats, Random::RandomGenerator& random) + : stats_(stats), random_(random) {} +  + // Upstream::LoadBalancerFactory +@@ -154,7 +154,7 @@ private: + // Ignore the params for the thread-aware LB. + LoadBalancerPtr create(LoadBalancerParams) override { return create(); } +  +- ClusterStats& stats_; ++ ClusterLbStats& stats_; + Random::RandomGenerator& random_; + absl::Mutex mutex_; + std::shared_ptr> per_priority_state_ ABSL_GUARDED_BY(mutex_); +diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc +index f8f06cdf59..63934a5453 100644 +--- a/source/common/upstream/upstream_impl.cc ++++ b/source/common/upstream/upstream_impl.cc +@@ -832,9 +832,9 @@ void MainPrioritySetImpl::updateCrossPriorityHostMap(const HostVector& hosts_add + } + } +  +-ClusterStats ClusterInfoImpl::generateStats(Stats::Scope& scope, +- const ClusterStatNames& stat_names) { +- return ClusterStats(stat_names, scope); ++ClusterTrafficStats ClusterInfoImpl::generateStats(Stats::Scope& scope, ++ const ClusterTrafficStatNames& stat_names) { ++ return {stat_names, scope}; + } +  + ClusterRequestResponseSizeStats ClusterInfoImpl::generateRequestResponseSizeStats( +@@ -845,13 +845,13 @@ ClusterRequestResponseSizeStats ClusterInfoImpl::generateRequestResponseSizeStat + ClusterLoadReportStats + ClusterInfoImpl::generateLoadReportStats(Stats::Scope& scope, + const ClusterLoadReportStatNames& stat_names) { +- return ClusterLoadReportStats(stat_names, scope); ++ return {stat_names, scope}; + } +  + ClusterTimeoutBudgetStats + ClusterInfoImpl::generateTimeoutBudgetStats(Stats::Scope& scope, + const ClusterTimeoutBudgetStatNames& stat_names) { +- return ClusterTimeoutBudgetStats(stat_names, scope); ++ return {stat_names, scope}; + } +  + // Implements the FactoryContext interface required by network filters. +@@ -978,6 +978,10 @@ ClusterInfoImpl::ClusterInfoImpl( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, per_connection_buffer_limit_bytes, 1024 * 1024)), + socket_matcher_(std::move(socket_matcher)), stats_scope_(std::move(stats_scope)), + stats_(generateStats(*stats_scope_, factory_context.clusterManager().clusterStatNames())), ++ config_update_stats_(factory_context.clusterManager().clusterConfigUpdateStatNames(), ++ *stats_scope_), ++ lb_stats_(factory_context.clusterManager().clusterLbStatNames(), *stats_scope_), ++ endpoint_stats_(factory_context.clusterManager().clusterEndpointStatNames(), *stats_scope_), + load_report_stats_store_(stats_scope_->symbolTable()), + load_report_stats_(generateLoadReportStats( + load_report_stats_store_, factory_context.clusterManager().clusterLoadReportStatNames())), +@@ -1322,7 +1326,7 @@ ClusterImplBase::ClusterImplBase( + priority_update_cb_ = priority_set_.addPriorityUpdateCb( + [this](uint32_t, const HostVector& hosts_added, const HostVector& hosts_removed) { + if (!hosts_added.empty() || !hosts_removed.empty()) { +- info_->stats().membership_change_.inc(); ++ info_->endpointStats().membership_change_.inc(); + } +  + uint32_t healthy_hosts = 0; +@@ -1335,10 +1339,10 @@ ClusterImplBase::ClusterImplBase( + degraded_hosts += host_set->degradedHosts().size(); + excluded_hosts += host_set->excludedHosts().size(); + } +- info_->stats().membership_total_.set(hosts); +- info_->stats().membership_healthy_.set(healthy_hosts); +- info_->stats().membership_degraded_.set(degraded_hosts); +- info_->stats().membership_excluded_.set(excluded_hosts); ++ info_->endpointStats().membership_total_.set(hosts); ++ info_->endpointStats().membership_healthy_.set(healthy_hosts); ++ info_->endpointStats().membership_degraded_.set(degraded_hosts); ++ info_->endpointStats().membership_excluded_.set(excluded_hosts); + }); + } +  +@@ -1417,8 +1421,15 @@ void ClusterImplBase::onPreInitComplete() { + void ClusterImplBase::onInitDone() { + if (health_checker_ && pending_initialize_health_checks_ == 0) { + for (auto& host_set : prioritySet().hostSetsPerPriority()) { +- pending_initialize_health_checks_ += host_set->hosts().size(); ++ for (auto& host : host_set->hosts()) { ++ if (host->disableActiveHealthCheck()) { ++ continue; ++ } ++ ++pending_initialize_health_checks_; ++ } + } ++ ENVOY_LOG(debug, "Cluster onInitDone pending initialize health check count {}", ++ pending_initialize_health_checks_); +  + // TODO(mattklein123): Remove this callback when done. + health_checker_->addHostCheckCompleteCb([this](HostSharedPtr, HealthTransition) -> void { +@@ -1769,8 +1780,9 @@ void PriorityStateManager::updateClusterPrioritySet( + for (const HostSharedPtr& host : *hosts) { + // Take into consideration when a non-EDS cluster has active health checking, i.e. to mark all + // the hosts unhealthy (host->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC)) and then fire +- // update callbacks to start the health checking process. +- if (health_checker_flag.has_value()) { ++ // update callbacks to start the health checking process. The endpoint with disabled active ++ // health check should not be set FAILED_ACTIVE_HC here. ++ if (health_checker_flag.has_value() && !host->disableActiveHealthCheck()) { + host->healthFlagSet(health_checker_flag.value()); + } + hosts_per_locality[host->locality()].push_back(host); +@@ -1851,6 +1863,9 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( + // Keep track of hosts for which locality is changed. + absl::flat_hash_set hosts_with_updated_locality_for_current_priority( + current_priority_hosts.size()); ++ // Keep track of hosts for which active health check flag is changed. ++ absl::flat_hash_set hosts_with_active_health_check_flag_changed( ++ current_priority_hosts.size()); + HostVector final_hosts; + for (const HostSharedPtr& host : new_hosts) { + // To match a new host with an existing host means comparing their addresses. +@@ -1877,7 +1892,14 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( + hosts_with_updated_locality_for_current_priority.emplace(existing_host->first); + } +  +- const bool skip_inplace_host_update = health_check_address_changed || locality_changed; ++ const bool active_health_check_flag_changed = ++ (health_checker_ != nullptr && existing_host_found && ++ existing_host->second->disableActiveHealthCheck() != host->disableActiveHealthCheck()); ++ if (active_health_check_flag_changed) { ++ hosts_with_active_health_check_flag_changed.emplace(existing_host->first); ++ } ++ const bool skip_inplace_host_update = ++ health_check_address_changed || locality_changed || active_health_check_flag_changed; +  + // When there is a match and we decided to do in-place update, we potentially update the + // host's health check flag and metadata. Afterwards, the host is pushed back into the +@@ -1940,7 +1962,7 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( + } +  + // If we are depending on a health checker, we initialize to unhealthy. +- if (health_checker_ != nullptr) { ++ if (health_checker_ != nullptr && !host->disableActiveHealthCheck()) { + host->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); +  + // If we want to exclude hosts until they have been health checked, mark them with +@@ -1992,7 +2014,8 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( + erase_from = std::remove_if( + current_priority_hosts.begin(), current_priority_hosts.end(), + [&all_new_hosts, &new_hosts_for_current_priority, +- &hosts_with_updated_locality_for_current_priority, &final_hosts, ++ &hosts_with_updated_locality_for_current_priority, ++ &hosts_with_active_health_check_flag_changed, &final_hosts, + &max_host_weight](const HostSharedPtr& p) { + // This host has already been added as a new host in the + // new_hosts_for_current_priority. Return false here to make sure that host +@@ -2001,6 +2024,10 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( + return false; + } +  ++ if (hosts_with_active_health_check_flag_changed.contains(p->address()->asString())) { ++ return false; ++ } ++ + if (all_new_hosts.contains(p->address()->asString()) && + !new_hosts_for_current_priority.contains(p->address()->asString())) { + // If the address is being completely deleted from this priority, but is +@@ -2012,8 +2039,11 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( + return false; + } +  +- if (!(p->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC) || +- p->healthFlagGet(Host::HealthFlag::FAILED_EDS_HEALTH))) { ++ // PENDING_DYNAMIC_REMOVAL doesn't apply for the host with disabled active ++ // health check, the host is removed immediately from this priority. ++ if ((!(p->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC) || ++ p->healthFlagGet(Host::HealthFlag::FAILED_EDS_HEALTH))) && ++ !p->disableActiveHealthCheck()) { + if (p->weight() > max_host_weight) { + max_host_weight = p->weight(); + } +@@ -2029,7 +2059,7 @@ bool BaseDynamicClusterImpl::updateDynamicHostList( +  + // At this point we've accounted for all the new hosts as well the hosts that previously + // existed in this priority. +- info_->stats().max_host_weight_.set(max_host_weight); ++ info_->endpointStats().max_host_weight_.set(max_host_weight); +  + // Whatever remains in current_priority_hosts should be removed. + if (!hosts_added_to_current_priority.empty() || !current_priority_hosts.empty()) { +@@ -2054,21 +2084,21 @@ getDnsLookupFamilyFromCluster(const envoy::config::cluster::v3::Cluster& cluster +  + void reportUpstreamCxDestroy(const Upstream::HostDescriptionConstSharedPtr& host, + Network::ConnectionEvent event) { +- host->cluster().stats().upstream_cx_destroy_.inc(); ++ host->cluster().trafficStats().upstream_cx_destroy_.inc(); + if (event == Network::ConnectionEvent::RemoteClose) { +- host->cluster().stats().upstream_cx_destroy_remote_.inc(); ++ host->cluster().trafficStats().upstream_cx_destroy_remote_.inc(); + } else { +- host->cluster().stats().upstream_cx_destroy_local_.inc(); ++ host->cluster().trafficStats().upstream_cx_destroy_local_.inc(); + } + } +  + void reportUpstreamCxDestroyActiveRequest(const Upstream::HostDescriptionConstSharedPtr& host, + Network::ConnectionEvent event) { +- host->cluster().stats().upstream_cx_destroy_with_active_rq_.inc(); ++ host->cluster().trafficStats().upstream_cx_destroy_with_active_rq_.inc(); + if (event == Network::ConnectionEvent::RemoteClose) { +- host->cluster().stats().upstream_cx_destroy_remote_with_active_rq_.inc(); ++ host->cluster().trafficStats().upstream_cx_destroy_remote_with_active_rq_.inc(); + } else { +- host->cluster().stats().upstream_cx_destroy_local_with_active_rq_.inc(); ++ host->cluster().trafficStats().upstream_cx_destroy_local_with_active_rq_.inc(); + } + } +  +diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h +index 3807d86961..e44289d419 100644 +--- a/source/common/upstream/upstream_impl.h ++++ b/source/common/upstream/upstream_impl.h +@@ -266,6 +266,7 @@ public: + TimeSource& time_source) + : HostDescriptionImpl(cluster, hostname, address, metadata, locality, health_check_config, + priority, time_source), ++ disable_active_health_check_(health_check_config.disable_active_health_check()), + health_status_(health_status) { + // This EDS flags setting is still necessary for stats, configuration dump, canonical + // coarseHealth() etc. +@@ -273,6 +274,11 @@ public: + HostImpl::weight(initial_weight); + } +  ++ 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; ++ } ++ + // Upstream::Host + std::vector> + counters() const override { +@@ -359,6 +365,7 @@ private: +  + std::atomic health_flags_{}; + 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. +@@ -703,8 +710,8 @@ public: + TransportSocketMatcherPtr&& socket_matcher, Stats::ScopeSharedPtr&& stats_scope, + bool added_via_api, Server::Configuration::TransportSocketFactoryContext&); +  +- static ClusterStats generateStats(Stats::Scope& scope, +- const ClusterStatNames& cluster_stat_names); ++ static ClusterTrafficStats generateStats(Stats::Scope& scope, ++ const ClusterTrafficStatNames& cluster_stat_names); + static ClusterLoadReportStats + generateLoadReportStats(Stats::Scope& scope, const ClusterLoadReportStatNames& stat_names); + static ClusterCircuitBreakersStats +@@ -791,7 +798,10 @@ public: + const std::string& observabilityName() const override { return observability_name_; } + ResourceManager& resourceManager(ResourcePriority priority) const override; + TransportSocketMatcher& transportSocketMatcher() const override { return *socket_matcher_; } +- ClusterStats& stats() const override { return stats_; } ++ ClusterTrafficStats& trafficStats() const override { return stats_; } ++ ClusterConfigUpdateStats& configUpdateStats() const override { return config_update_stats_; } ++ ClusterLbStats& lbStats() const override { return lb_stats_; } ++ ClusterEndpointStats& endpointStats() const override { return endpoint_stats_; } + Stats::Scope& statsScope() const override { return *stats_scope_; } +  + ClusterRequestResponseSizeStatsOptRef requestResponseSizeStats() const override { +@@ -905,7 +915,10 @@ private: + const uint32_t per_connection_buffer_limit_bytes_; + TransportSocketMatcherPtr socket_matcher_; + Stats::ScopeSharedPtr stats_scope_; +- mutable ClusterStats stats_; ++ mutable ClusterTrafficStats stats_; ++ mutable ClusterConfigUpdateStats config_update_stats_; ++ mutable ClusterLbStats lb_stats_; ++ mutable ClusterEndpointStats endpoint_stats_; + Stats::IsolatedStoreImpl load_report_stats_store_; + mutable ClusterLoadReportStats load_report_stats_; + const std::unique_ptr optional_cluster_stats_; +diff --git a/source/extensions/clusters/aggregate/cluster.cc b/source/extensions/clusters/aggregate/cluster.cc +index 78caa5e8a1..bebac8ac11 100644 +--- a/source/extensions/clusters/aggregate/cluster.cc ++++ b/source/extensions/clusters/aggregate/cluster.cc +@@ -110,7 +110,7 @@ void AggregateClusterLoadBalancer::refresh(OptRef excluded_cl + PriorityContextPtr priority_context = linearizePrioritySet(excluded_cluster); + if (!priority_context->priority_set_.hostSetsPerPriority().empty()) { + load_balancer_ = std::make_unique( +- *priority_context, parent_info_->stats(), runtime_, random_, parent_info_->lbConfig()); ++ *priority_context, parent_info_->lbStats(), runtime_, random_, parent_info_->lbConfig()); + } else { + load_balancer_ = nullptr; + } +diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h +index 90a267a606..7d0a215d6e 100644 +--- a/source/extensions/clusters/aggregate/cluster.h ++++ b/source/extensions/clusters/aggregate/cluster.h +@@ -91,10 +91,10 @@ private: + // priority set could be empty, we cannot initialize LoadBalancerBase when priority set is empty. + class LoadBalancerImpl : public Upstream::LoadBalancerBase { + public: +- LoadBalancerImpl(const PriorityContext& priority_context, Upstream::ClusterStats& stats, ++ LoadBalancerImpl(const PriorityContext& priority_context, Upstream::ClusterLbStats& lb_stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +- : Upstream::LoadBalancerBase(priority_context.priority_set_, stats, runtime, random, ++ : Upstream::LoadBalancerBase(priority_context.priority_set_, lb_stats, runtime, random, + common_config), + priority_context_(priority_context) {} +  +diff --git a/source/extensions/clusters/logical_dns/logical_dns_cluster.cc b/source/extensions/clusters/logical_dns/logical_dns_cluster.cc +index ace5cd9e64..fe7107fc87 100644 +--- a/source/extensions/clusters/logical_dns/logical_dns_cluster.cc ++++ b/source/extensions/clusters/logical_dns/logical_dns_cluster.cc +@@ -106,7 +106,7 @@ LogicalDnsCluster::~LogicalDnsCluster() { +  + void LogicalDnsCluster::startResolve() { + ENVOY_LOG(debug, "starting async DNS resolution for {}", dns_address_); +- info_->stats().update_attempt_.inc(); ++ info_->configUpdateStats().update_attempt_.inc(); +  + active_dns_query_ = dns_resolver_->resolve( + dns_address_, dns_lookup_family_, +@@ -121,7 +121,7 @@ void LogicalDnsCluster::startResolve() { + // cluster does not update. This ensures that a potentially previously resolved address does + // not stabilize back to 0 hosts. + if (status == Network::DnsResolver::ResolutionStatus::Success && !response.empty()) { +- info_->stats().update_success_.inc(); ++ info_->configUpdateStats().update_success_.inc(); + const auto addrinfo = response.front().addrInfo(); + // TODO(mattklein123): Move port handling into the DNS interface. + ASSERT(addrinfo.address_ != nullptr); +@@ -166,7 +166,7 @@ void LogicalDnsCluster::startResolve() { + ENVOY_LOG(debug, "DNS refresh rate reset for {}, refresh rate {} ms", dns_address_, + final_refresh_rate.count()); + } else { +- info_->stats().update_failure_.inc(); ++ info_->configUpdateStats().update_failure_.inc(); + final_refresh_rate = + std::chrono::milliseconds(failure_backoff_strategy_->nextBackOffMs()); + ENVOY_LOG(debug, "DNS refresh rate reset for {}, (failure) refresh rate {} ms", +diff --git a/source/extensions/clusters/original_dst/original_dst_cluster.cc b/source/extensions/clusters/original_dst/original_dst_cluster.cc +index 32dc599774..5f9f636df1 100644 +--- a/source/extensions/clusters/original_dst/original_dst_cluster.cc ++++ b/source/extensions/clusters/original_dst/original_dst_cluster.cc +@@ -124,7 +124,7 @@ OriginalDstCluster::LoadBalancer::requestOverrideHost(LoadBalancerContext* conte + if (request_host == nullptr) { + ENVOY_LOG(debug, "original_dst_load_balancer: invalid override header value. {}", + request_override_host); +- parent_->info()->stats().original_dst_host_invalid_.inc(); ++ parent_->info()->trafficStats().original_dst_host_invalid_.inc(); + return nullptr; + } + ENVOY_LOG(debug, "Using request override host {}.", request_override_host); +diff --git a/source/extensions/clusters/redis/redis_cluster.cc b/source/extensions/clusters/redis/redis_cluster.cc +index cb98b84f5c..db0ce184bf 100644 +--- a/source/extensions/clusters/redis/redis_cluster.cc ++++ b/source/extensions/clusters/redis/redis_cluster.cc +@@ -142,7 +142,7 @@ void RedisCluster::onClusterSlotUpdate(ClusterSlotsSharedPtr&& slots) { + })); + updateAllHosts(hosts_added, hosts_removed, localityLbEndpoint().priority()); + } else { +- info_->stats().update_no_rebuild_.inc(); ++ info_->configUpdateStats().update_no_rebuild_.inc(); + } +  + // TODO(hyang): If there is an initialize callback, fire it now. Note that if the +@@ -190,9 +190,9 @@ void RedisCluster::DnsDiscoveryResolveTarget::startResolveDns() { + ENVOY_LOG(trace, "async DNS resolution complete for {}", dns_address_); + if (status == Network::DnsResolver::ResolutionStatus::Failure || response.empty()) { + if (status == Network::DnsResolver::ResolutionStatus::Failure) { +- parent_.info_->stats().update_failure_.inc(); ++ parent_.info_->configUpdateStats().update_failure_.inc(); + } else { +- parent_.info_->stats().update_empty_.inc(); ++ parent_.info_->configUpdateStats().update_empty_.inc(); + } +  + if (!resolve_timer_) { +@@ -273,7 +273,7 @@ void RedisCluster::RedisDiscoverySession::registerDiscoveryAddress( + } +  + void RedisCluster::RedisDiscoverySession::startResolveRedis() { +- parent_.info_->stats().update_attempt_.inc(); ++ parent_.info_->configUpdateStats().update_attempt_.inc(); + // If a resolution is currently in progress, skip it. + if (current_request_) { + ENVOY_LOG(debug, "redis cluster slot request is already in progress for '{}'", +@@ -311,9 +311,9 @@ void RedisCluster::RedisDiscoverySession::startResolveRedis() { + void RedisCluster::RedisDiscoverySession::updateDnsStats( + Network::DnsResolver::ResolutionStatus status, bool empty_response) { + if (status == Network::DnsResolver::ResolutionStatus::Failure) { +- parent_.info_->stats().update_failure_.inc(); ++ parent_.info_->configUpdateStats().update_failure_.inc(); + } else if (empty_response) { +- parent_.info_->stats().update_empty_.inc(); ++ parent_.info_->configUpdateStats().update_empty_.inc(); + } + } +  +@@ -558,7 +558,7 @@ bool RedisCluster::RedisDiscoverySession::validateCluster( + void RedisCluster::RedisDiscoverySession::onUnexpectedResponse( + const NetworkFilters::Common::Redis::RespValuePtr& value) { + ENVOY_LOG(warn, "Unexpected response to cluster slot command: {}", value->toString()); +- this->parent_.info_->stats().update_failure_.inc(); ++ this->parent_.info_->configUpdateStats().update_failure_.inc(); + resolve_timer_->enableTimer(parent_.cluster_refresh_rate_); + } +  +@@ -569,7 +569,7 @@ void RedisCluster::RedisDiscoverySession::onFailure() { + auto client_to_delete = client_map_.find(current_host_address_); + client_to_delete->second->client_->close(); + } +- parent_.info()->stats().update_failure_.inc(); ++ parent_.info()->configUpdateStats().update_failure_.inc(); + resolve_timer_->enableTimer(parent_.cluster_refresh_rate_); + } +  +diff --git a/source/extensions/clusters/strict_dns/strict_dns_cluster.cc b/source/extensions/clusters/strict_dns/strict_dns_cluster.cc +index ed091974f4..5ec87de45a 100644 +--- a/source/extensions/clusters/strict_dns/strict_dns_cluster.cc ++++ b/source/extensions/clusters/strict_dns/strict_dns_cluster.cc +@@ -105,7 +105,7 @@ StrictDnsClusterImpl::ResolveTarget::~ResolveTarget() { +  + void StrictDnsClusterImpl::ResolveTarget::startResolve() { + ENVOY_LOG(trace, "starting async DNS resolution for {}", dns_address_); +- parent_.info_->stats().update_attempt_.inc(); ++ parent_.info_->configUpdateStats().update_attempt_.inc(); +  + active_query_ = parent_.dns_resolver_->resolve( + dns_address_, parent_.dns_lookup_family_, +@@ -117,7 +117,7 @@ void StrictDnsClusterImpl::ResolveTarget::startResolve() { + std::chrono::milliseconds final_refresh_rate = parent_.dns_refresh_rate_ms_; +  + if (status == Network::DnsResolver::ResolutionStatus::Success) { +- parent_.info_->stats().update_success_.inc(); ++ parent_.info_->configUpdateStats().update_success_.inc(); +  + HostVector new_hosts; + std::chrono::seconds ttl_refresh_rate = std::chrono::seconds::max(); +@@ -164,7 +164,7 @@ void StrictDnsClusterImpl::ResolveTarget::startResolve() { +  + parent_.updateAllHosts(hosts_added, hosts_removed, locality_lb_endpoints_.priority()); + } else { +- parent_.info_->stats().update_no_rebuild_.inc(); ++ parent_.info_->configUpdateStats().update_no_rebuild_.inc(); + } +  + // reset failure backoff strategy because there was a success. +@@ -179,7 +179,7 @@ void StrictDnsClusterImpl::ResolveTarget::startResolve() { + ENVOY_LOG(debug, "DNS refresh rate reset for {}, refresh rate {} ms", dns_address_, + final_refresh_rate.count()); + } else { +- parent_.info_->stats().update_failure_.inc(); ++ parent_.info_->configUpdateStats().update_failure_.inc(); +  + final_refresh_rate = + std::chrono::milliseconds(parent_.failure_backoff_strategy_->nextBackOffMs()); +diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl +index 56519a62d0..9f4467964b 100644 +--- a/source/extensions/extensions_build_config.bzl ++++ b/source/extensions/extensions_build_config.bzl +@@ -75,6 +75,12 @@ EXTENSIONS = { +  + "envoy.matching.common_inputs.environment_variable": "//source/extensions/matching/common_inputs/environment_variable:config", +  ++ # ++ # Matching actions ++ # ++ ++ "envoy.matching.actions.format_string": "//source/extensions/matching/actions/format_string:config", ++ + # + # HTTP filters + # +@@ -381,6 +387,7 @@ EXTENSIONS = { + EXTENSION_CONFIG_VISIBILITY = ["//:extension_config", "//:contrib_library", "//:examples_library", "//:mobile_library"] + EXTENSION_PACKAGE_VISIBILITY = ["//:extension_library", "//:contrib_library", "//:examples_library", "//:mobile_library"] + CONTRIB_EXTENSION_PACKAGE_VISIBILITY = ["//:contrib_library"] ++MOBILE_PACKAGE_VISIBILITY = ["//:mobile_library"] +  + # Set this variable to true to disable alwayslink for envoy_cc_library. + # TODO(alyssawilk) audit uses of this in source/ and migrate all libraries to extensions. +diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml +index c4c4fc9f52..c8238def04 100644 +--- a/source/extensions/extensions_metadata.yaml ++++ b/source/extensions/extensions_metadata.yaml +@@ -1321,3 +1321,10 @@ envoy.matching.custom_matchers.trie_matcher: + status: alpha + type_urls: + - xds.type.matcher.v3.IPMatcher ++envoy.matching.actions.format_string: ++ categories: ++ - envoy.matching.action ++ security_posture: unknown ++ status: alpha ++ type_urls: ++ - envoy.config.core.v3.SubstitutionFormatString +diff --git a/source/extensions/filters/common/rbac/matchers.cc b/source/extensions/filters/common/rbac/matchers.cc +index 74a7e34bd1..10ca0b60ed 100644 +--- a/source/extensions/filters/common/rbac/matchers.cc ++++ b/source/extensions/filters/common/rbac/matchers.cc +@@ -76,6 +76,8 @@ MatcherConstSharedPtr Matcher::create(const envoy::config::rbac::v3::Principal& + return std::make_shared(principal.not_id()); + case envoy::config::rbac::v3::Principal::IdentifierCase::kUrlPath: + return std::make_shared(principal.url_path()); ++ case envoy::config::rbac::v3::Principal::IdentifierCase::kFilterState: ++ return std::make_shared(principal.filter_state()); + case envoy::config::rbac::v3::Principal::IdentifierCase::IDENTIFIER_NOT_SET: + break; // Fall through to PANIC. + } +@@ -233,6 +235,11 @@ bool MetadataMatcher::matches(const Network::Connection&, const Envoy::Http::Req + return matcher_.match(info.dynamicMetadata()); + } +  ++bool FilterStateMatcher::matches(const Network::Connection&, const Envoy::Http::RequestHeaderMap&, ++ const StreamInfo::StreamInfo& info) const { ++ return matcher_.match(info.filterState()); ++} ++ + bool PolicyMatcher::matches(const Network::Connection& connection, + const Envoy::Http::RequestHeaderMap& headers, + const StreamInfo::StreamInfo& info) const { +diff --git a/source/extensions/filters/common/rbac/matchers.h b/source/extensions/filters/common/rbac/matchers.h +index a43dbce5c7..2a2b41ea7f 100644 +--- a/source/extensions/filters/common/rbac/matchers.h ++++ b/source/extensions/filters/common/rbac/matchers.h +@@ -239,6 +239,18 @@ private: + const Envoy::Matchers::MetadataMatcher matcher_; + }; +  ++class FilterStateMatcher : public Matcher { ++public: ++ FilterStateMatcher(const envoy::type::matcher::v3::FilterStateMatcher& matcher) ++ : matcher_(matcher) {} ++ ++ bool matches(const Network::Connection&, const Envoy::Http::RequestHeaderMap&, ++ const StreamInfo::StreamInfo& info) const override; ++ ++private: ++ const Envoy::Matchers::FilterStateMatcher matcher_; ++}; ++ + /** + * Perform a match against the request server from the client's connection + * request. This is typically TLS SNI. +diff --git a/source/extensions/filters/http/cache/cache_entry_utils.cc b/source/extensions/filters/http/cache/cache_entry_utils.cc +index 116f450a67..dd5eb27120 100644 +--- a/source/extensions/filters/http/cache/cache_entry_utils.cc ++++ b/source/extensions/filters/http/cache/cache_entry_utils.cc +@@ -28,6 +28,58 @@ std::ostream& operator<<(std::ostream& os, CacheEntryStatus status) { + return os << cacheEntryStatusString(status); + } +  ++namespace { ++const absl::flat_hash_set headersNotToUpdate() { ++ CONSTRUCT_ON_FIRST_USE( ++ absl::flat_hash_set, ++ // Content range should not be changed upon validation ++ Http::Headers::get().ContentRange, ++ ++ // Headers that describe the body content should never be updated. ++ Http::Headers::get().ContentLength, ++ ++ // It does not make sense for this level of the code to be updating the ETag, when ++ // presumably the cached_response_headers reflect this specific ETag. ++ Http::CustomHeaders::get().Etag, ++ ++ // We don't update the cached response on a Vary; we just delete it ++ // entirely. So don't bother copying over the Vary header. ++ Http::CustomHeaders::get().Vary); ++} ++} // namespace ++ ++void applyHeaderUpdate(const Http::ResponseHeaderMap& new_headers, ++ Http::ResponseHeaderMap& headers_to_update) { ++ // Assumptions: ++ // 1. The internet is fast, i.e. we get the result as soon as the server sends it. ++ // Race conditions would not be possible because we are always processing up-to-date data. ++ // 2. No key collision for etag. Therefore, if etag matches it's the same resource. ++ // 3. Backend is correct. etag is being used as a unique identifier to the resource ++ ++ // use other header fields provided in the new response to replace all instances ++ // of the corresponding header fields in the stored response ++ ++ // `updatedHeaderFields` makes sure each field is only removed when we update the header ++ // field for the first time to handle the case where incoming headers have repeated values ++ absl::flat_hash_set updatedHeaderFields; ++ new_headers.iterate( ++ [&headers_to_update, &updatedHeaderFields]( ++ const Http::HeaderEntry& incoming_response_header) -> Http::HeaderMap::Iterate { ++ Http::LowerCaseString lower_case_key{incoming_response_header.key().getStringView()}; ++ absl::string_view incoming_value{incoming_response_header.value().getStringView()}; ++ if (headersNotToUpdate().contains(lower_case_key)) { ++ return Http::HeaderMap::Iterate::Continue; ++ } ++ if (!updatedHeaderFields.contains(lower_case_key)) { ++ headers_to_update.setCopy(lower_case_key, incoming_value); ++ updatedHeaderFields.insert(lower_case_key); ++ } else { ++ headers_to_update.addCopy(lower_case_key, incoming_value); ++ } ++ return Http::HeaderMap::Iterate::Continue; ++ }); ++} ++ + } // namespace Cache + } // namespace HttpFilters + } // namespace Extensions +diff --git a/source/extensions/filters/http/cache/cache_entry_utils.h b/source/extensions/filters/http/cache/cache_entry_utils.h +index d49f76990e..d43c4f6a41 100644 +--- a/source/extensions/filters/http/cache/cache_entry_utils.h ++++ b/source/extensions/filters/http/cache/cache_entry_utils.h +@@ -44,6 +44,17 @@ enum class CacheEntryStatus { + absl::string_view cacheEntryStatusString(CacheEntryStatus s); + std::ostream& operator<<(std::ostream& os, CacheEntryStatus status); +  ++// For an updateHeaders operation, new headers must be merged into existing headers ++// for the cache entry. This helper function performs that merge correctly, i.e. ++// - if a header appears in new_headers, prior values for that header are erased ++// from headers_to_update. ++// - if a header appears more than once in new_headers, all new values are added ++// to headers_to_update. ++// - headers that are not supposed to be updated during updateHeaders operations ++// (etag, content-length, content-range, vary) are ignored. ++void applyHeaderUpdate(const Http::ResponseHeaderMap& new_headers, ++ Http::ResponseHeaderMap& headers_to_update); ++ + } // namespace Cache + } // namespace HttpFilters + } // namespace Extensions +diff --git a/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc b/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc +index 944d340866..53d8b26403 100644 +--- a/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc ++++ b/source/extensions/filters/http/cache/simple_http_cache/simple_http_cache.cc +@@ -142,24 +142,6 @@ LookupContextPtr SimpleHttpCache::makeLookupContext(LookupRequest&& request, + return std::make_unique(*this, std::move(request)); + } +  +-const absl::flat_hash_set SimpleHttpCache::headersNotToUpdate() { +- CONSTRUCT_ON_FIRST_USE( +- absl::flat_hash_set, +- // Content range should not be changed upon validation +- Http::Headers::get().ContentRange, +- +- // Headers that describe the body content should never be updated. +- Http::Headers::get().ContentLength, +- +- // It does not make sense for this level of the code to be updating the ETag, when +- // presumably the cached_response_headers reflect this specific ETag. +- Http::CustomHeaders::get().Etag, +- +- // We don't update the cached response on a Vary; we just delete it +- // entirely. So don't bother copying over the Vary header. +- Http::CustomHeaders::get().Vary); +-} +- + void SimpleHttpCache::updateHeaders(const LookupContext& lookup_context, + const Http::ResponseHeaderMap& response_headers, + const ResponseMetadata& metadata, +@@ -187,34 +169,7 @@ void SimpleHttpCache::updateHeaders(const LookupContext& lookup_context, + } + Entry& entry = iter->second; +  +- // Assumptions: +- // 1. The internet is fast, i.e. we get the result as soon as the server sends it. +- // Race conditions would not be possible because we are always processing up-to-date data. +- // 2. No key collision for etag. Therefore, if etag matches it's the same resource. +- // 3. Backend is correct. etag is being used as a unique identifier to the resource +- +- // use other header fields provided in the new response to replace all instances +- // of the corresponding header fields in the stored response +- +- // `updatedHeaderFields` makes sure each field is only removed when we update the header +- // field for the first time to handle the case where incoming headers have repeated values +- absl::flat_hash_set updatedHeaderFields; +- response_headers.iterate( +- [&entry, &updatedHeaderFields]( +- const Http::HeaderEntry& incoming_response_header) -> Http::HeaderMap::Iterate { +- Http::LowerCaseString lower_case_key{incoming_response_header.key().getStringView()}; +- absl::string_view incoming_value{incoming_response_header.value().getStringView()}; +- if (headersNotToUpdate().contains(lower_case_key)) { +- return Http::HeaderMap::Iterate::Continue; +- } +- if (!updatedHeaderFields.contains(lower_case_key)) { +- entry.response_headers_->setCopy(lower_case_key, incoming_value); +- updatedHeaderFields.insert(lower_case_key); +- } else { +- entry.response_headers_->addCopy(lower_case_key, incoming_value); +- } +- return Http::HeaderMap::Iterate::Continue; +- }); ++ applyHeaderUpdate(response_headers, *entry.response_headers_); + entry.metadata_ = metadata; + on_complete(true); + } +diff --git a/source/extensions/filters/http/health_check/health_check.cc b/source/extensions/filters/http/health_check/health_check.cc +index 08b3cb8b29..8a6bb39fdd 100644 +--- a/source/extensions/filters/http/health_check/health_check.cc ++++ b/source/extensions/filters/http/health_check/health_check.cc +@@ -145,8 +145,8 @@ void HealthCheckFilter::onComplete() { +  + break; + } +- const auto& stats = cluster->info()->stats(); +- const uint64_t membership_total = stats.membership_total_.value(); ++ const auto& endpoint_stats = cluster->info()->endpointStats(); ++ const uint64_t membership_total = endpoint_stats.membership_total_.value(); + if (membership_total == 0) { + // If the cluster exists but is empty, consider the service unhealthy unless + // the specified minimum percent healthy for the cluster happens to be zero. +@@ -160,7 +160,8 @@ void HealthCheckFilter::onComplete() { + } + // In the general case, consider the service unhealthy if fewer than the + // specified percentage of the servers in the cluster are available (healthy + degraded). +- if ((100UL * (stats.membership_healthy_.value() + stats.membership_degraded_.value())) < ++ if ((100UL * (endpoint_stats.membership_healthy_.value() + ++ endpoint_stats.membership_degraded_.value())) < + membership_total * min_healthy_percentage) { + final_status = Http::Code::ServiceUnavailable; + details = &RcDetails::get().HealthCheckClusterUnhealthy; +diff --git a/source/extensions/filters/network/common/redis/client_impl.cc b/source/extensions/filters/network/common/redis/client_impl.cc +index bb3add9ab8..c2958cbfbb 100644 +--- a/source/extensions/filters/network/common/redis/client_impl.cc ++++ b/source/extensions/filters/network/common/redis/client_impl.cc +@@ -77,9 +77,9 @@ ClientImpl::ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dis + flush_timer_(dispatcher.createTimer([this]() { flushBufferAndResetTimer(); })), + time_source_(dispatcher.timeSource()), redis_command_stats_(redis_command_stats), + scope_(scope), is_transaction_client_(is_transaction_client) { +- host->cluster().stats().upstream_cx_total_.inc(); ++ host->cluster().trafficStats().upstream_cx_total_.inc(); + host->stats().cx_total_.inc(); +- host->cluster().stats().upstream_cx_active_.inc(); ++ host->cluster().trafficStats().upstream_cx_active_.inc(); + host->stats().cx_active_.inc(); + connect_or_op_timer_->enableTimer(host->cluster().connectTimeout()); + } +@@ -87,7 +87,7 @@ ClientImpl::ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dis + ClientImpl::~ClientImpl() { + ASSERT(pending_requests_.empty()); + ASSERT(connection_->state() == Network::Connection::State::Closed); +- host_->cluster().stats().upstream_cx_active_.dec(); ++ host_->cluster().trafficStats().upstream_cx_active_.dec(); + host_->stats().cx_active_.dec(); + } +  +@@ -141,10 +141,10 @@ PoolRequest* ClientImpl::makeRequest(const RespValue& request, ClientCallbacks& + void ClientImpl::onConnectOrOpTimeout() { + putOutlierEvent(Upstream::Outlier::Result::LocalOriginTimeout); + if (connected_) { +- host_->cluster().stats().upstream_rq_timeout_.inc(); ++ host_->cluster().trafficStats().upstream_rq_timeout_.inc(); + host_->stats().rq_timeout_.inc(); + } else { +- host_->cluster().stats().upstream_cx_connect_timeout_.inc(); ++ host_->cluster().trafficStats().upstream_cx_connect_timeout_.inc(); + host_->stats().cx_connect_fail_.inc(); + } +  +@@ -156,7 +156,7 @@ void ClientImpl::onData(Buffer::Instance& data) { + decoder_->decode(data); + } catch (ProtocolError&) { + putOutlierEvent(Upstream::Outlier::Result::ExtOriginRequestFailed); +- host_->cluster().stats().upstream_cx_protocol_error_.inc(); ++ host_->cluster().trafficStats().upstream_cx_protocol_error_.inc(); + host_->stats().rq_error_.inc(); + connection_->close(Network::ConnectionCloseType::NoFlush); + } +@@ -185,7 +185,7 @@ void ClientImpl::onEvent(Network::ConnectionEvent event) { + if (!request.canceled_) { + request.callbacks_.onFailure(); + } else { +- host_->cluster().stats().upstream_rq_cancelled_.inc(); ++ host_->cluster().trafficStats().upstream_rq_cancelled_.inc(); + } + pending_requests_.pop_front(); + } +@@ -198,7 +198,7 @@ void ClientImpl::onEvent(Network::ConnectionEvent event) { + } +  + if (event == Network::ConnectionEvent::RemoteClose && !connected_) { +- host_->cluster().stats().upstream_cx_connect_fail_.inc(); ++ host_->cluster().trafficStats().upstream_cx_connect_fail_.inc(); + host_->stats().cx_connect_fail_.inc(); + } + } +@@ -221,7 +221,7 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { + // result in closing the connection. + pending_requests_.pop_front(); + if (canceled) { +- host_->cluster().stats().upstream_rq_cancelled_.inc(); ++ host_->cluster().trafficStats().upstream_rq_cancelled_.inc(); + } else if (config_.enableRedirection() && !is_transaction_client_ && + (value->type() == Common::Redis::RespType::Error)) { + std::vector err = StringUtil::splitToken(value->asString(), " ", false); +@@ -263,14 +263,14 @@ ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, ClientCallbacks& + command_request_timer_ = parent_.redis_command_stats_->createCommandTimer( + parent_.scope_, command_, parent_.time_source_); + } +- parent.host_->cluster().stats().upstream_rq_total_.inc(); ++ parent.host_->cluster().trafficStats().upstream_rq_total_.inc(); + parent.host_->stats().rq_total_.inc(); +- parent.host_->cluster().stats().upstream_rq_active_.inc(); ++ parent.host_->cluster().trafficStats().upstream_rq_active_.inc(); + parent.host_->stats().rq_active_.inc(); + } +  + ClientImpl::PendingRequest::~PendingRequest() { +- parent_.host_->cluster().stats().upstream_rq_active_.dec(); ++ parent_.host_->cluster().trafficStats().upstream_rq_active_.dec(); + parent_.host_->stats().rq_active_.dec(); + } +  +diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +index 4619fc2d51..4795ab4fec 100644 +--- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc ++++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +@@ -454,7 +454,7 @@ void InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& v + host_address); + auto host = host_; + onResponse(std::move(resp_value_)); +- host->cluster().stats().upstream_internal_redirect_failed_total_.inc(); ++ host->cluster().trafficStats().upstream_internal_redirect_failed_total_.inc(); + } else { + doRedirection(std::move(resp_value_), + formatAddress(*result.host_info_.value()->address()->ip()), ask_redirection_); +@@ -470,7 +470,7 @@ void InstanceImpl::PendingRequest::onRedirection(Common::Redis::RespValuePtr&& v + host_address); + auto host = host_; + onResponse(std::move(resp_value_)); +- host->cluster().stats().upstream_internal_redirect_failed_total_.inc(); ++ host->cluster().trafficStats().upstream_internal_redirect_failed_total_.inc(); + return; + } + PANIC_DUE_TO_CORRUPT_ENUM; +@@ -487,7 +487,7 @@ void InstanceImpl::PendingRequest::onLoadDnsCacheComplete( + ENVOY_LOG(debug, "DNS lookup failed"); + auto host = host_; + onResponse(std::move(resp_value_)); +- host->cluster().stats().upstream_internal_redirect_failed_total_.inc(); ++ host->cluster().trafficStats().upstream_internal_redirect_failed_total_.inc(); + } else { + doRedirection(std::move(resp_value_), formatAddress(*host_info->address()->ip()), + ask_redirection_); +@@ -509,16 +509,16 @@ void InstanceImpl::PendingRequest::doRedirection(Common::Redis::RespValuePtr&& v + !parent_.makeRequestToHost(host_address, Common::Redis::Utility::AskingRequest::instance(), + null_client_callbacks)) { + onResponse(std::move(value)); +- host->cluster().stats().upstream_internal_redirect_failed_total_.inc(); ++ host->cluster().trafficStats().upstream_internal_redirect_failed_total_.inc(); + } else { + request_handler_ = + parent_.makeRequestToHost(host_address, getRequest(incoming_request_), *this); + if (!request_handler_) { + onResponse(std::move(value)); +- host->cluster().stats().upstream_internal_redirect_failed_total_.inc(); ++ host->cluster().trafficStats().upstream_internal_redirect_failed_total_.inc(); + } else { + parent_.refresh_manager_->onRedirection(parent_.cluster_name_); +- host->cluster().stats().upstream_internal_redirect_succeeded_total_.inc(); ++ host->cluster().trafficStats().upstream_internal_redirect_succeeded_total_.inc(); + } + } + } +diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +index 118d206bc7..19ab2d997b 100644 +--- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc ++++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +@@ -128,7 +128,7 @@ UdpProxyFilter::ClusterInfo::createSession(Network::UdpRecvData::LocalPeerAddres + .connections() + .canCreate()) { + ENVOY_LOG(debug, "cannot create new connection."); +- cluster_.info()->stats().upstream_cx_overflow_.inc(); ++ cluster_.info()->trafficStats().upstream_cx_overflow_.inc(); + return nullptr; + } +  +@@ -139,7 +139,7 @@ UdpProxyFilter::ClusterInfo::createSession(Network::UdpRecvData::LocalPeerAddres + auto host = chooseHost(addresses.peer_); + if (host == nullptr) { + ENVOY_LOG(debug, "cannot find any valid host."); +- cluster_.info()->stats().upstream_cx_none_healthy_.inc(); ++ cluster_.info()->trafficStats().upstream_cx_none_healthy_.inc(); + return nullptr; + } + return createSessionWithHost(std::move(addresses), host); +@@ -212,7 +212,7 @@ UdpProxyFilter::PerPacketLoadBalancingClusterInfo::onData(Network::UdpRecvData& + auto host = chooseHost(data.addresses_.peer_); + if (host == nullptr) { + ENVOY_LOG(debug, "cannot find any valid host."); +- cluster_.info()->stats().upstream_cx_none_healthy_.inc(); ++ cluster_.info()->trafficStats().upstream_cx_none_healthy_.inc(); + return Network::FilterStatus::StopIteration; + } +  +@@ -407,7 +407,7 @@ void UdpProxyFilter::ActiveSession::write(const Buffer::Instance& buffer) { + cluster_.cluster_stats_.sess_tx_errors_.inc(); + } else { + cluster_.cluster_stats_.sess_tx_datagrams_.inc(); +- cluster_.cluster_.info()->stats().upstream_cx_tx_bytes_total_.add(buffer_length); ++ cluster_.cluster_.info()->trafficStats().upstream_cx_tx_bytes_total_.add(buffer_length); + } + } +  +@@ -420,7 +420,7 @@ void UdpProxyFilter::ActiveSession::processPacket(Network::Address::InstanceCons + const uint64_t buffer_length = buffer->length(); +  + cluster_.cluster_stats_.sess_rx_datagrams_.inc(); +- cluster_.cluster_.info()->stats().upstream_cx_rx_bytes_total_.add(buffer_length); ++ cluster_.cluster_.info()->trafficStats().upstream_cx_rx_bytes_total_.add(buffer_length); +  + Network::UdpSendData data{addresses_.local_->ip(), *addresses_.peer_, *buffer}; + const Api::IoCallUint64Result rc = cluster_.filter_.read_callbacks_->udpListener().send(data); +diff --git a/source/extensions/matching/actions/format_string/BUILD b/source/extensions/matching/actions/format_string/BUILD +new file mode 100644 +index 0000000000..0861595683 +--- /dev/null ++++ b/source/extensions/matching/actions/format_string/BUILD +@@ -0,0 +1,26 @@ ++load( ++ "//bazel:envoy_build_system.bzl", ++ "envoy_cc_extension", ++ "envoy_extension_package", ++) ++ ++licenses(["notice"]) # Apache 2 ++ ++envoy_extension_package() ++ ++envoy_cc_extension( ++ name = "config", ++ srcs = ["config.cc"], ++ hdrs = ["config.h"], ++ deps = [ ++ "//envoy/formatter:substitution_formatter_interface", ++ "//envoy/matcher:matcher_interface", ++ "//envoy/registry", ++ "//envoy/server:factory_context_interface", ++ "//source/common/formatter:substitution_format_string_lib", ++ "//source/common/http:header_map_lib", ++ "//source/common/matcher:matcher_lib", ++ "//source/server:filter_chain_manager_lib", ++ "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ++ ], ++) +diff --git a/source/extensions/matching/actions/format_string/config.cc b/source/extensions/matching/actions/format_string/config.cc +new file mode 100644 +index 0000000000..038e6ed6bd +--- /dev/null ++++ b/source/extensions/matching/actions/format_string/config.cc +@@ -0,0 +1,46 @@ ++#include "source/extensions/matching/actions/format_string/config.h" ++ ++#include "envoy/registry/registry.h" ++ ++#include "source/common/formatter/substitution_format_string.h" ++#include "source/common/http/header_map_impl.h" ++#include "source/common/protobuf/utility.h" ++ ++namespace Envoy { ++namespace Extensions { ++namespace Matching { ++namespace Actions { ++namespace FormatString { ++ ++const Network::FilterChain* ActionImpl::get(const Server::FilterChainsByName& filter_chains_by_name, ++ const StreamInfo::StreamInfo& info) const { ++ const std::string name = ++ formatter_->format(*Http::StaticEmptyHeaders::get().request_headers, ++ *Http::StaticEmptyHeaders::get().response_headers, ++ *Http::StaticEmptyHeaders::get().response_trailers, info, ""); ++ const auto chain_match = filter_chains_by_name.find(name); ++ if (chain_match != filter_chains_by_name.end()) { ++ return chain_match->second.get(); ++ } ++ return nullptr; ++} ++ ++Matcher::ActionFactoryCb ++ActionFactory::createActionFactoryCb(const Protobuf::Message& proto_config, ++ Server::FilterChainActionFactoryContext& context, ++ ProtobufMessage::ValidationVisitor& validator) { ++ const auto& config = ++ MessageUtil::downcastAndValidate( ++ proto_config, validator); ++ Formatter::FormatterConstSharedPtr formatter = ++ Formatter::SubstitutionFormatStringUtils::fromProtoConfig(config, context); ++ return [formatter]() { return std::make_unique(formatter); }; ++} ++ ++REGISTER_FACTORY(ActionFactory, Matcher::ActionFactory); ++ ++} // namespace FormatString ++} // namespace Actions ++} // namespace Matching ++} // namespace Extensions ++} // namespace Envoy +diff --git a/source/extensions/matching/actions/format_string/config.h b/source/extensions/matching/actions/format_string/config.h +new file mode 100644 +index 0000000000..7006a3e4aa +--- /dev/null ++++ b/source/extensions/matching/actions/format_string/config.h +@@ -0,0 +1,45 @@ ++#pragma once ++ ++#include "envoy/config/core/v3/substitution_format_string.pb.h" ++#include "envoy/config/core/v3/substitution_format_string.pb.validate.h" ++#include "envoy/formatter/substitution_formatter.h" ++#include "envoy/matcher/matcher.h" ++#include "envoy/server/factory_context.h" ++ ++#include "source/common/matcher/matcher.h" ++#include "source/server/filter_chain_manager_impl.h" ++ ++namespace Envoy { ++namespace Extensions { ++namespace Matching { ++namespace Actions { ++namespace FormatString { ++ ++class ActionImpl : public Matcher::ActionBase { ++public: ++ ActionImpl(const Formatter::FormatterConstSharedPtr& formatter) : formatter_(formatter) {} ++ const Network::FilterChain* get(const Server::FilterChainsByName& filter_chains_by_name, ++ const StreamInfo::StreamInfo& info) const override; ++ ++private: ++ const Formatter::FormatterConstSharedPtr formatter_; ++}; ++ ++class ActionFactory : public Matcher::ActionFactory { ++public: ++ std::string name() const override { return "envoy.matching.actions.format_string"; } ++ Matcher::ActionFactoryCb ++ createActionFactoryCb(const Protobuf::Message& proto_config, ++ Server::FilterChainActionFactoryContext& context, ++ ProtobufMessage::ValidationVisitor& validator) override; ++ ProtobufTypes::MessagePtr createEmptyConfigProto() override { ++ return std::make_unique(); ++ } ++}; ++ ++} // namespace FormatString ++} // namespace Actions ++} // namespace Matching ++} // namespace Extensions ++} // namespace Envoy +diff --git a/source/extensions/stat_sinks/common/statsd/statsd.cc b/source/extensions/stat_sinks/common/statsd/statsd.cc +index e18c54cd2f..47eef9a1d2 100644 +--- a/source/extensions/stat_sinks/common/statsd/statsd.cc ++++ b/source/extensions/stat_sinks/common/statsd/statsd.cc +@@ -330,7 +330,7 @@ void TcpStatsdSink::TlsSink::write(Buffer::Instance& buffer) { + // since if we stay over, the other threads will eventually kill their connections too. + // TODO(mattklein123): The use of the stat is somewhat of a hack, and should be replaced with + // real flow control callbacks once they are available. +- if (parent_.cluster_info_->stats().upstream_cx_tx_bytes_buffered_.value() > ++ if (parent_.cluster_info_->trafficStats().upstream_cx_tx_bytes_buffered_.value() > + MAX_BUFFERED_STATS_BYTES) { + if (connection_) { + connection_->close(Network::ConnectionCloseType::NoFlush); +@@ -354,11 +354,12 @@ void TcpStatsdSink::TlsSink::write(Buffer::Instance& buffer) { +  + connection_ = std::move(info.connection_); + connection_->addConnectionCallbacks(*this); +- connection_->setConnectionStats({parent_.cluster_info_->stats().upstream_cx_rx_bytes_total_, +- parent_.cluster_info_->stats().upstream_cx_rx_bytes_buffered_, +- parent_.cluster_info_->stats().upstream_cx_tx_bytes_total_, +- parent_.cluster_info_->stats().upstream_cx_tx_bytes_buffered_, +- &parent_.cluster_info_->stats().bind_errors_, nullptr}); ++ connection_->setConnectionStats( ++ {parent_.cluster_info_->trafficStats().upstream_cx_rx_bytes_total_, ++ parent_.cluster_info_->trafficStats().upstream_cx_rx_bytes_buffered_, ++ parent_.cluster_info_->trafficStats().upstream_cx_tx_bytes_total_, ++ parent_.cluster_info_->trafficStats().upstream_cx_tx_bytes_buffered_, ++ &parent_.cluster_info_->trafficStats().bind_errors_, nullptr}); + connection_->connect(); + } +  +diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc +index c912c42cf2..dac8610db6 100644 +--- a/source/extensions/stat_sinks/hystrix/hystrix.cc ++++ b/source/extensions/stat_sinks/hystrix/hystrix.cc +@@ -90,7 +90,7 @@ uint64_t HystrixSink::getRollingValue(RollingWindow rolling_window) { +  + void HystrixSink::updateRollingWindowMap(const Upstream::ClusterInfo& cluster_info, + ClusterStatsCache& cluster_stats_cache) { +- Upstream::ClusterStats& cluster_stats = cluster_info.stats(); ++ Upstream::ClusterTrafficStats& cluster_stats = cluster_info.trafficStats(); + Stats::Scope& cluster_stats_scope = cluster_info.statsScope(); +  + // Combining timeouts+retries - retries are counted as separate requests +diff --git a/source/server/filter_chain_manager_impl.cc b/source/server/filter_chain_manager_impl.cc +index b92ebf490b..8624ae3911 100644 +--- a/source/server/filter_chain_manager_impl.cc ++++ b/source/server/filter_chain_manager_impl.cc +@@ -31,30 +31,29 @@ Network::Address::InstanceConstSharedPtr fakeAddress() { + Network::Utility::parseInternetAddress("255.255.255.255")); + } +  +-struct FilterChainNameAction : public Matcher::ActionBase { +- explicit FilterChainNameAction(Network::DrainableFilterChainSharedPtr chain) : chain_(chain) {} +- const Network::DrainableFilterChainSharedPtr chain_; ++struct FilterChainNameAction ++ : public Matcher::ActionBase { ++ explicit FilterChainNameAction(const std::string& name) : name_(name) {} ++ const Network::FilterChain* get(const FilterChainsByName& filter_chains_by_name, ++ const StreamInfo::StreamInfo&) const override { ++ const auto chain_match = filter_chains_by_name.find(name_); ++ if (chain_match != filter_chains_by_name.end()) { ++ return chain_match->second.get(); ++ } ++ return nullptr; ++ } ++ const std::string name_; + }; +  +-using FilterChainActionFactoryContext = +- absl::flat_hash_map; +- + class FilterChainNameActionFactory : public Matcher::ActionFactory, + Logger::Loggable { + public: + std::string name() const override { return "filter-chain-name"; } + Matcher::ActionFactoryCb createActionFactoryCb(const Protobuf::Message& config, +- FilterChainActionFactoryContext& filter_chains, ++ FilterChainActionFactoryContext&, + ProtobufMessage::ValidationVisitor&) override { +- Network::DrainableFilterChainSharedPtr chain = nullptr; + const auto& name = dynamic_cast(config); +- const auto chain_match = filter_chains.find(name.value()); +- if (chain_match != filter_chains.end()) { +- chain = chain_match->second; +- } else { +- ENVOY_LOG(debug, "matcher API points to an absent filter chain '{}'", name.value()); +- } +- return [chain]() { return std::make_unique(chain); }; ++ return [value = name.value()]() { return std::make_unique(value); }; + } + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); +@@ -216,7 +215,7 @@ void FilterChainManagerImpl::addFilterChains( + MessageUtil> + filter_chains; + uint32_t new_filter_chain_size = 0; +- absl::flat_hash_map filter_chains_by_name; ++ FilterChainsByName filter_chains_by_name; +  + for (const auto& filter_chain : filter_chain_span) { + const auto& filter_chain_match = filter_chain->filter_chain_match(); +@@ -312,9 +311,11 @@ void FilterChainManagerImpl::addFilterChains( + context_creator); + // Construct matcher if it is present in the listener configuration. + if (filter_chain_matcher) { ++ filter_chains_by_name_ = filter_chains_by_name; + FilterChainNameActionValidationVisitor validation_visitor; + Matcher::MatchTreeFactory factory( +- filter_chains_by_name, parent_context_.getServerFactoryContext(), validation_visitor); ++ parent_context_.getServerFactoryContext(), parent_context_.getServerFactoryContext(), ++ validation_visitor); + matcher_ = factory.create(*filter_chain_matcher)(); + } + ENVOY_LOG(debug, "new fc_contexts has {} filter chains, including {} newly built", +@@ -583,14 +584,14 @@ FilterChainManagerImpl::findFilterChain(const Network::ConnectionSocket& socket, +  + const Network::FilterChain* + FilterChainManagerImpl::findFilterChainUsingMatcher(const Network::ConnectionSocket& socket, +- const StreamInfo::StreamInfo&) const { ++ const StreamInfo::StreamInfo& info) const { + Network::Matching::MatchingDataImpl data(socket); + const auto& match_result = Matcher::evaluateMatch(*matcher_, data); + ASSERT(match_result.match_state_ == Matcher::MatchState::MatchComplete, + "Matching must complete for network streams."); + if (match_result.result_) { + const auto result = match_result.result_(); +- return result->getTyped().chain_.get(); ++ return result->getTyped().get(filter_chains_by_name_, info); + } + return default_filter_chain_.get(); + } +diff --git a/source/server/filter_chain_manager_impl.h b/source/server/filter_chain_manager_impl.h +index 8814d091f9..4ae38de72a 100644 +--- a/source/server/filter_chain_manager_impl.h ++++ b/source/server/filter_chain_manager_impl.h +@@ -100,6 +100,15 @@ private: + std::atomic is_draining_{false}; + }; +  ++using FilterChainActionFactoryContext = Configuration::ServerFactoryContext; ++using FilterChainsByName = absl::flat_hash_map; ++ ++class FilterChainBaseAction : public Matcher::Action { ++public: ++ virtual const Network::FilterChain* get(const FilterChainsByName& filter_chains_by_name, ++ const StreamInfo::StreamInfo& info) const PURE; ++}; ++ + class FilterChainImpl : public Network::DrainableFilterChain { + public: + FilterChainImpl(Network::DownstreamTransportSocketFactoryPtr&& transport_socket_factory, +@@ -395,6 +404,9 @@ private: +  + // Matcher selecting the filter chain name. + Matcher::MatchTreePtr matcher_; ++ ++ // Index filter chains by name, used by the matcher actions. ++ FilterChainsByName filter_chains_by_name_; + }; + } // namespace Server + } // namespace Envoy +diff --git a/test/common/common/BUILD b/test/common/common/BUILD +index 9a9d483594..5655a09485 100644 +--- a/test/common/common/BUILD ++++ b/test/common/common/BUILD +@@ -231,6 +231,7 @@ envoy_cc_test( + "//source/common/common:matchers_lib", + "//source/common/config:metadata_lib", + "//source/common/protobuf:utility_lib", ++ "//source/common/stream_info:filter_state_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/type/matcher/v3:pkg_cc_proto", +diff --git a/test/common/common/matchers_test.cc b/test/common/common/matchers_test.cc +index 581967af8b..0c152a5982 100644 +--- a/test/common/common/matchers_test.cc ++++ b/test/common/common/matchers_test.cc +@@ -7,6 +7,7 @@ + #include "source/common/common/matchers.h" + #include "source/common/config/metadata.h" + #include "source/common/protobuf/protobuf.h" ++#include "source/common/stream_info/filter_state_impl.h" +  + #include "test/test_common/utility.h" +  +@@ -475,6 +476,59 @@ TEST(PathMatcher, MatchRegexPath) { + EXPECT_FALSE(Matchers::PathMatcher(matcher).match("/regez#regex")); + } +  ++TEST(FilterStateMatcher, MatchAbsentFilterState) { ++ envoy::type::matcher::v3::FilterStateMatcher matcher; ++ matcher.set_key("test.key"); ++ matcher.mutable_string_match()->set_exact("exact"); ++ StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); ++ EXPECT_FALSE(Matchers::FilterStateMatcher(matcher).match(filter_state)); ++} ++ ++class TestObject : public StreamInfo::FilterState::Object { ++public: ++ TestObject(absl::optional value) : value_(value) {} ++ absl::optional serializeAsString() const override { return value_; } ++ ++private: ++ absl::optional value_; ++}; ++ ++TEST(FilterStateMatcher, MatchFilterStateWithoutString) { ++ const std::string key = "test.key"; ++ envoy::type::matcher::v3::FilterStateMatcher matcher; ++ 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); ++ EXPECT_FALSE(Matchers::FilterStateMatcher(matcher).match(filter_state)); ++} ++ ++TEST(FilterStateMatcher, MatchFilterStateDifferentString) { ++ const std::string key = "test.key"; ++ const std::string value = "exact_value"; ++ envoy::type::matcher::v3::FilterStateMatcher matcher; ++ 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("different")), ++ StreamInfo::FilterState::StateType::ReadOnly); ++ EXPECT_FALSE(Matchers::FilterStateMatcher(matcher).match(filter_state)); ++} ++ ++TEST(FilterStateMatcher, MatchFilterState) { ++ const std::string key = "test.key"; ++ const std::string value = "exact_value"; ++ envoy::type::matcher::v3::FilterStateMatcher matcher; ++ 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); ++ EXPECT_TRUE(Matchers::FilterStateMatcher(matcher).match(filter_state)); ++} ++ + } // namespace + } // namespace Matcher + } // namespace Envoy +diff --git a/test/common/conn_pool/conn_pool_base_test.cc b/test/common/conn_pool/conn_pool_base_test.cc +index 3554583027..fb53487b21 100644 +--- a/test/common/conn_pool/conn_pool_base_test.cc ++++ b/test/common/conn_pool/conn_pool_base_test.cc +@@ -374,13 +374,13 @@ TEST_F(ConnPoolImplDispatcherBaseTest, MaxConnectionDurationBusy) { + // Verify that advancing to just before the connection duration timeout doesn't drain the + // connection. + advanceTimeAndRun(max_connection_duration_ - 1); +- EXPECT_EQ(0, pool_.host()->cluster().stats().upstream_cx_max_duration_reached_.value()); ++ EXPECT_EQ(0, pool_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.value()); + EXPECT_EQ(ActiveClient::State::Busy, clients_.back()->state()); +  + // Verify that advancing past the connection duration timeout drains the connection, + // because there's a busy client. + advanceTimeAndRun(2); +- EXPECT_EQ(1, pool_.host()->cluster().stats().upstream_cx_max_duration_reached_.value()); ++ EXPECT_EQ(1, pool_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.value()); + EXPECT_EQ(ActiveClient::State::Draining, clients_.back()->state()); + closeStream(); + } +@@ -395,13 +395,13 @@ TEST_F(ConnPoolImplDispatcherBaseTest, MaxConnectionDurationReady) { + // Verify that advancing to just before the connection duration timeout doesn't close the + // connection. + advanceTimeAndRun(max_connection_duration_ - 1); +- EXPECT_EQ(0, pool_.host()->cluster().stats().upstream_cx_max_duration_reached_.value()); ++ EXPECT_EQ(0, pool_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.value()); + EXPECT_EQ(ActiveClient::State::Ready, clients_.back()->state()); +  + // Verify that advancing past the connection duration timeout closes the connection, + // because there's nothing to drain. + advanceTimeAndRun(2); +- EXPECT_EQ(1, pool_.host()->cluster().stats().upstream_cx_max_duration_reached_.value()); ++ EXPECT_EQ(1, pool_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.value()); + } +  + TEST_F(ConnPoolImplDispatcherBaseTest, MaxConnectionDurationAlreadyDraining) { +@@ -411,7 +411,7 @@ TEST_F(ConnPoolImplDispatcherBaseTest, MaxConnectionDurationAlreadyDraining) { + // Verify that advancing past the connection duration timeout does nothing to an active client + // that is already draining. + advanceTimeAndRun(max_connection_duration_ + 1); +- EXPECT_EQ(0, pool_.host()->cluster().stats().upstream_cx_max_duration_reached_.value()); ++ EXPECT_EQ(0, pool_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.value()); + EXPECT_EQ(ActiveClient::State::Draining, clients_.back()->state()); + closeStream(); + } +@@ -423,7 +423,7 @@ TEST_F(ConnPoolImplDispatcherBaseTest, MaxConnectionDurationAlreadyClosed) { + // Verify that advancing past the connection duration timeout does nothing to the active + // client that is already closed. + advanceTimeAndRun(max_connection_duration_ + 1); +- EXPECT_EQ(0, pool_.host()->cluster().stats().upstream_cx_max_duration_reached_.value()); ++ EXPECT_EQ(0, pool_.host()->cluster().trafficStats().upstream_cx_max_duration_reached_.value()); + } +  + TEST_F(ConnPoolImplDispatcherBaseTest, MaxConnectionDurationCallbackWhileClosedBug) { +@@ -565,7 +565,7 @@ TEST_F(ConnPoolImplDispatcherBaseTest, ConnectedZeroRttSendsEarlyData) { + pool_.onUpstreamReadyForEarlyData(client_ref); +  + CHECK_STATE(1 /*active*/, 0 /*pending*/, concurrent_streams_ - 1 /*connecting capacity*/); +- EXPECT_EQ(1, pool_.host()->cluster().stats().upstream_rq_0rtt_.value()); ++ EXPECT_EQ(1, pool_.host()->cluster().trafficStats().upstream_rq_0rtt_.value()); +  + EXPECT_NE(nullptr, pool_.newStreamImpl(context_, /*can_send_early_data=*/false)); + CHECK_STATE(1 /*active*/, 1 /*pending*/, concurrent_streams_ - 1 /*connecting capacity*/); +@@ -574,7 +574,7 @@ TEST_F(ConnPoolImplDispatcherBaseTest, ConnectedZeroRttSendsEarlyData) { + clients_.back()->onEvent(Network::ConnectionEvent::Connected); +  + CHECK_STATE(2 /*active*/, 0 /*pending*/, 0 /*connecting capacity*/); +- EXPECT_EQ(1, pool_.host()->cluster().stats().upstream_rq_0rtt_.value()); ++ EXPECT_EQ(1, pool_.host()->cluster().trafficStats().upstream_rq_0rtt_.value()); +  + // Clean up. + closeStreamAndDrainClient(); +@@ -596,12 +596,12 @@ TEST_F(ConnPoolImplDispatcherBaseTest, EarlyDataStreamsReachConcurrentStreamLimi + pool_.onUpstreamReadyForEarlyData(client_ref); +  + CHECK_STATE(1 /*active*/, 0 /*pending*/, concurrent_streams_ - 1 /*connecting capacity*/); +- EXPECT_EQ(1, pool_.host()->cluster().stats().upstream_rq_0rtt_.value()); ++ EXPECT_EQ(1, pool_.host()->cluster().trafficStats().upstream_rq_0rtt_.value()); +  + EXPECT_CALL(pool_, onPoolReady); + EXPECT_EQ(nullptr, pool_.newStreamImpl(context_, /*can_send_early_data=*/true)); + CHECK_STATE(2 /*active*/, 0 /*pending*/, concurrent_streams_ - 2 /*connecting capacity*/); +- EXPECT_EQ(2, pool_.host()->cluster().stats().upstream_rq_0rtt_.value()); ++ EXPECT_EQ(2, pool_.host()->cluster().trafficStats().upstream_rq_0rtt_.value()); + EXPECT_EQ(ActiveClient::State::Busy, clients_.back()->state()); +  + // After 1 stream gets closed, the client should transit to ReadyForEarlyData. +diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc +index 657dcc53c9..ac477115f1 100644 +--- a/test/common/http/http1/codec_impl_test.cc ++++ b/test/common/http/http1/codec_impl_test.cc +@@ -904,10 +904,6 @@ TEST_P(Http1ServerConnectionImplTest, Http10MultipleResponses) { +  + // Now send an HTTP/1.1 request and make sure the protocol is tracked correctly. + { +- TestRequestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, +- {":scheme", "http"}, +- {":path", "/foobar"}, +- {":method", "GET"}}; + Buffer::OwnedImpl buffer("GET /foobar HTTP/1.1\r\nHost: www.somewhere.com\r\n\r\n"); +  + Http::ResponseEncoder* response_encoder = nullptr; +@@ -1141,6 +1137,7 @@ TEST_P(Http1ServerConnectionImplTest, SimpleGet) { + auto status = codec_->dispatch(buffer); + EXPECT_TRUE(status.ok()); + EXPECT_EQ(0U, buffer.length()); ++ EXPECT_EQ(Protocol::Http11, codec_->protocol()); + } +  + // Test that if the stream is not created at the time an error is detected, it +@@ -1550,6 +1547,7 @@ TEST_P(Http1ServerConnectionImplTest, HeaderOnlyResponse) { + TestResponseHeaderMapImpl headers{{":status", "200"}}; + response_encoder->encodeHeaders(headers, true); + EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); ++ EXPECT_EQ(Protocol::Http11, codec_->protocol()); + } +  + // As with Http1ClientConnectionImplTest.LargeHeaderRequestEncode but validate +@@ -3766,5 +3764,25 @@ TEST_P(Http1ServerConnectionImplTest, ParseUrl) { + } + } +  ++// The client's HTTP parser does not have access to the request. ++// Test that it determines the HTTP version based on the response correctly. ++TEST_P(Http1ClientConnectionImplTest, ResponseHttpVersion) { ++ for (Protocol http_version : {Protocol::Http10, Protocol::Http11}) { ++ initialize(); ++ NiceMock response_decoder; ++ 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_(_, true)); ++ Buffer::OwnedImpl response(http_version == Protocol::Http10 ++ ? "HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n" ++ : "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); ++ auto status = codec_->dispatch(response); ++ EXPECT_TRUE(status.ok()); ++ EXPECT_EQ(http_version, codec_->protocol()); ++ } ++} ++ + } // namespace Http + } // namespace Envoy +diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc +index 1794081394..c86d46af9e 100644 +--- a/test/common/http/utility_test.cc ++++ b/test/common/http/utility_test.cc +@@ -27,6 +27,28 @@ using testing::Return; +  + namespace Envoy { + namespace Http { ++namespace { ++ ++void sendLocalReplyTestHelper(const bool& is_reset, StreamDecoderFilterCallbacks& callbacks, ++ const Utility::LocalReplyData& local_reply_data) { ++ absl::string_view details; ++ if (callbacks.streamInfo().responseCodeDetails().has_value()) { ++ details = callbacks.streamInfo().responseCodeDetails().value(); ++ }; ++ ++ Utility::sendLocalReply( ++ is_reset, ++ Utility::EncodeFunctions{nullptr, nullptr, ++ [&](ResponseHeaderMapPtr&& headers, bool end_stream) -> void { ++ callbacks.encodeHeaders(std::move(headers), end_stream, details); ++ }, ++ [&](Buffer::Instance& data, bool end_stream) -> void { ++ callbacks.encodeData(data, end_stream); ++ }}, ++ local_reply_data); ++} ++ ++} // namespace +  + TEST(HttpUtility, parseQueryString) { + EXPECT_EQ(Utility::QueryParams(), Utility::parseQueryString("/hello")); +@@ -843,7 +865,7 @@ TEST(HttpUtility, SendLocalReply) { + EXPECT_CALL(callbacks, encodeHeaders_(_, false)); + EXPECT_CALL(callbacks, encodeData(_, true)); + EXPECT_CALL(callbacks, streamInfo()); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + is_reset, callbacks, + Utility::LocalReplyData{false, Http::Code::PayloadTooLarge, "large", absl::nullopt, false}); + } +@@ -862,7 +884,7 @@ TEST(HttpUtility, SendLocalGrpcReply) { + EXPECT_NE(headers.GrpcMessage(), nullptr); + EXPECT_EQ(headers.getGrpcMessageValue(), "large"); + })); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + is_reset, callbacks, + Utility::LocalReplyData{true, Http::Code::PayloadTooLarge, "large", absl::nullopt, false}); + } +@@ -881,7 +903,7 @@ TEST(HttpUtility, SendLocalGrpcReplyGrpcStatusAlreadyExists) { + EXPECT_NE(headers.GrpcMessage(), nullptr); + EXPECT_EQ(headers.getGrpcMessageValue(), "large"); + })); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + is_reset, callbacks, + Utility::LocalReplyData{true, Http::Code::PayloadTooLarge, "large", + Grpc::Status::WellKnownGrpcStatus::InvalidArgument, false}); +@@ -940,7 +962,7 @@ TEST(HttpUtility, SendLocalGrpcReplyWithUpstreamJsonPayload) { + const auto& encoded = Utility::PercentEncoding::encode(json); + EXPECT_EQ(headers.getGrpcMessageValue(), encoded); + })); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + is_reset, callbacks, + Utility::LocalReplyData{true, Http::Code::Unauthorized, json, absl::nullopt, false}); + } +@@ -955,7 +977,7 @@ TEST(HttpUtility, RateLimitedGrpcStatus) { + EXPECT_EQ(headers.getGrpcStatusValue(), + std::to_string(enumToInt(Grpc::Status::WellKnownGrpcStatus::Unavailable))); + })); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + false, callbacks, + Utility::LocalReplyData{true, Http::Code::TooManyRequests, "", absl::nullopt, false}); +  +@@ -965,7 +987,7 @@ TEST(HttpUtility, RateLimitedGrpcStatus) { + EXPECT_EQ(headers.getGrpcStatusValue(), + std::to_string(enumToInt(Grpc::Status::WellKnownGrpcStatus::ResourceExhausted))); + })); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + false, callbacks, + Utility::LocalReplyData{true, Http::Code::TooManyRequests, "", + absl::make_optional( +@@ -982,7 +1004,7 @@ TEST(HttpUtility, SendLocalReplyDestroyedEarly) { + is_reset = true; + })); + EXPECT_CALL(callbacks, encodeData(_, true)).Times(0); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + is_reset, callbacks, + Utility::LocalReplyData{false, Http::Code::PayloadTooLarge, "large", absl::nullopt, false}); + } +@@ -995,7 +1017,7 @@ TEST(HttpUtility, SendLocalReplyHeadRequest) { + .WillOnce(Invoke([&](const ResponseHeaderMap& headers, bool) -> void { + EXPECT_EQ(headers.getContentLengthValue(), fmt::format("{}", strlen("large"))); + })); +- Utility::sendLocalReply( ++ sendLocalReplyTestHelper( + is_reset, callbacks, + Utility::LocalReplyData{false, Http::Code::PayloadTooLarge, "large", absl::nullopt, true}); + } +diff --git a/test/common/matcher/test_utility.h b/test/common/matcher/test_utility.h +index 66d1ebcea6..b9e9482780 100644 +--- a/test/common/matcher/test_utility.h ++++ b/test/common/matcher/test_utility.h +@@ -243,7 +243,7 @@ void verifyImmediateMatch(const MatchTree::MatchResult& result, + EXPECT_EQ(nullptr, result.on_match_->matcher_); + EXPECT_NE(result.on_match_->action_cb_, nullptr); +  +- EXPECT_EQ(*static_cast(result.on_match_->action_cb_().get()), ++ EXPECT_EQ(result.on_match_->action_cb_().get()->getTyped(), + *stringValue(expected_value)); + } +  +diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc +index 5735ec57d0..21557b902c 100644 +--- a/test/common/network/connection_impl_test.cc ++++ b/test/common/network/connection_impl_test.cc +@@ -467,7 +467,7 @@ TEST_P(ConnectionImplTest, SetServerTransportSocketTimeout) { + auto server_connection = std::make_unique( + *mocks.dispatcher_, + std::make_unique(std::move(io_handle), nullptr, nullptr), +- std::move(mocks.transport_socket_), stream_info_, true); ++ std::move(mocks.transport_socket_), stream_info_); +  + EXPECT_CALL(*mock_timer, enableTimer(std::chrono::milliseconds(3 * 1000), _)); + Stats::MockCounter timeout_counter; +@@ -488,7 +488,7 @@ TEST_P(ConnectionImplTest, SetServerTransportSocketTimeoutAfterConnect) { + auto server_connection = std::make_unique( + *mocks.dispatcher_, + std::make_unique(std::move(io_handle), nullptr, nullptr), +- std::move(mocks.transport_socket_), stream_info_, true); ++ std::move(mocks.transport_socket_), stream_info_); +  + transport_socket->callbacks_->raiseEvent(ConnectionEvent::Connected); + // This should be a no-op. No timer should be created. +@@ -512,7 +512,7 @@ TEST_P(ConnectionImplTest, ServerTransportSocketTimeoutDisabledOnConnect) { + auto server_connection = std::make_unique( + *mocks.dispatcher_, + std::make_unique(std::move(io_handle), nullptr, nullptr), +- std::move(mocks.transport_socket_), stream_info_, true); ++ std::move(mocks.transport_socket_), stream_info_); +  + bool timer_destroyed = false; + mock_timer->timer_destroyed_ = &timer_destroyed; +@@ -2009,7 +2009,7 @@ TEST_P(ConnectionImplTest, NetworkConnectionDumpsWithoutAllocatingMemory) { + auto server_connection = std::make_unique( + *mocks.dispatcher_, + std::make_unique(std::move(io_handle), nullptr, nullptr), +- std::move(mocks.transport_socket_), stream_info_, true); ++ std::move(mocks.transport_socket_), stream_info_); +  + // Start measuring memory and dump state. + Stats::TestUtil::MemoryTest memory_test; +diff --git a/test/common/router/retry_state_impl_test.cc b/test/common/router/retry_state_impl_test.cc +index 65d47c7245..6abd42db62 100644 +--- a/test/common/router/retry_state_impl_test.cc ++++ b/test/common/router/retry_state_impl_test.cc +@@ -157,9 +157,9 @@ public: + EXPECT_EQ(RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryHeaders(response_headers, request_headers, header_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + } +  +@@ -217,10 +217,10 @@ TEST_F(RouterRetryStateImplTest, PolicyRefusedStream) { + state_->shouldRetryReset(remote_refused_stream_reset_, RetryState::Http3Used::No, + reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_.value()); + } +@@ -253,10 +253,10 @@ TEST_F(RouterRetryStateImplTest, PolicyAltProtocolPostHandshakeFailure) { + state_->shouldRetryReset(remote_refused_stream_reset_, RetryState::Http3Used::No, + reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_.value()); + } +@@ -300,10 +300,10 @@ TEST_F(RouterRetryStateImplTest, Policy5xxRemoteReset) { + EXPECT_EQ(RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(remote_reset_, RetryState::Http3Used::No, reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); + } +@@ -390,10 +390,10 @@ TEST_F(RouterRetryStateImplTest, PolicyGatewayErrorRemoteReset) { + EXPECT_EQ(RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(remote_reset_, RetryState::Http3Used::No, reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); + } +@@ -437,10 +437,10 @@ TEST_F(RouterRetryStateImplTest, Policy5xxRemote200RemoteReset) { + EXPECT_EQ(RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(remote_reset_, RetryState::Http3Used::No, reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); + } +@@ -532,7 +532,7 @@ TEST_F(RouterRetryStateImplTest, NoRetryUponTooEarlyStatusCodeWithDownstreamEarl + EXPECT_EQ(RetryStatus::No, + state_->shouldRetryHeaders(response_headers, request_headers, header_callback_)); +  +- EXPECT_EQ(0UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(0UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, route_stats_context_.stats().upstream_rq_retry_.value()); + } +@@ -828,10 +828,10 @@ TEST_F(RouterRetryStateImplTest, PolicyResetRemoteReset) { + EXPECT_EQ(RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(remote_reset_, RetryState::Http3Used::No, reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); + } +@@ -915,10 +915,10 @@ TEST_F(RouterRetryStateImplTest, RouteConfigNoRetriesAllowed) { + RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(connect_failure_, RetryState::Http3Used::Unknown, reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(0UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(0UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); + } +@@ -948,7 +948,7 @@ TEST_F(RouterRetryStateImplTest, NoAvailableRetries) { + EXPECT_EQ( + RetryStatus::NoOverflow, + state_->shouldRetryReset(connect_failure_, RetryState::Http3Used::Unknown, reset_callback_)); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_overflow_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_overflow_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_overflow_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_overflow_.value()); + } +@@ -988,9 +988,9 @@ TEST_F(RouterRetryStateImplTest, MaxRetriesHeader) { + RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(connect_failure_, RetryState::Http3Used::Unknown, reset_callback_)); +  +- EXPECT_EQ(3UL, cluster_.stats().upstream_rq_retry_.value()); +- EXPECT_EQ(0UL, cluster_.stats().upstream_rq_retry_success_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(3UL, cluster_.trafficStats().upstream_rq_retry_.value()); ++ EXPECT_EQ(0UL, cluster_.trafficStats().upstream_rq_retry_success_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(3UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, virtual_cluster_.stats().upstream_rq_retry_success_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); +@@ -1062,8 +1062,8 @@ TEST_F(RouterRetryStateImplTest, Backoff) { + EXPECT_EQ(RetryStatus::No, + state_->shouldRetryHeaders(response_headers, request_headers, header_callback_)); +  +- EXPECT_EQ(5UL, cluster_.stats().upstream_rq_retry_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_success_.value()); ++ EXPECT_EQ(5UL, cluster_.trafficStats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_success_.value()); + EXPECT_EQ(5UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_success_.value()); + EXPECT_EQ(5UL, route_stats_context_.stats().upstream_rq_retry_.value()); +@@ -1306,8 +1306,8 @@ TEST_F(RouterRetryStateImplTest, RateLimitedRetryBackoffStrategy) { + RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryHeaders(response_headers_reset_2, request_headers, header_callback_)); +  +- EXPECT_EQ(2UL, cluster_.stats().upstream_rq_retry_backoff_ratelimited_.value()); +- EXPECT_EQ(2UL, cluster_.stats().upstream_rq_retry_backoff_exponential_.value()); ++ EXPECT_EQ(2UL, cluster_.trafficStats().upstream_rq_retry_backoff_ratelimited_.value()); ++ EXPECT_EQ(2UL, cluster_.trafficStats().upstream_rq_retry_backoff_exponential_.value()); + } +  + TEST_F(RouterRetryStateImplTest, HostSelectionAttempts) { +@@ -1343,10 +1343,10 @@ TEST_F(RouterRetryStateImplTest, ZeroMaxRetriesHeader) { + RetryStatus::NoRetryLimitExceeded, + state_->shouldRetryReset(connect_failure_, RetryState::Http3Used::Unknown, reset_callback_)); +  +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(1UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(0UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(0UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, route_stats_context_.stats().upstream_rq_retry_.value()); + } +@@ -1367,10 +1367,10 @@ TEST_F(RouterRetryStateImplTest, NoPreferredOverLimitExceeded) { + EXPECT_EQ(RetryStatus::No, + state_->shouldRetryHeaders(good_response_headers, request_headers, header_callback_)); +  +- EXPECT_EQ(0UL, cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); ++ EXPECT_EQ(0UL, cluster_.trafficStats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(0UL, virtual_cluster_.stats().upstream_rq_retry_limit_exceeded_.value()); + EXPECT_EQ(0UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); +- EXPECT_EQ(1UL, cluster_.stats().upstream_rq_retry_.value()); ++ EXPECT_EQ(1UL, cluster_.trafficStats().upstream_rq_retry_.value()); + EXPECT_EQ(1UL, virtual_cluster_.stats().upstream_rq_retry_.value()); + EXPECT_EQ(0UL, route_stats_context_.stats().upstream_rq_retry_limit_exceeded_.value()); + } +diff --git a/test/common/upstream/eds_test.cc b/test/common/upstream/eds_test.cc +index 88fb82a0c9..2d0dbed694 100644 +--- a/test/common/upstream/eds_test.cc ++++ b/test/common/upstream/eds_test.cc +@@ -872,6 +872,118 @@ TEST_F(EdsTest, EndpointRemovalEdsFailButActiveHcSuccess) { + } + } +  ++// Verify the add and removal of hosts with disable active hc flag during eds update. ++TEST_F(EdsTest, DisableActiveHCEndpoints) { ++ envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; ++ cluster_load_assignment.set_cluster_name("fare"); ++ resetCluster(); ++ initialize(); ++ ++ auto health_checker = std::make_shared(); ++ EXPECT_CALL(*health_checker, start()); ++ EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)).Times(2); ++ cluster_->setHealthChecker(health_checker); ++ ++ auto add_endpoint = [&cluster_load_assignment](int port, bool disable_hc, bool healthy) { ++ auto* lb_endpoint = cluster_load_assignment.add_endpoints()->add_lb_endpoints(); ++ auto* endpoint = lb_endpoint->mutable_endpoint(); ++ auto* socket_address = endpoint->mutable_address()->mutable_socket_address(); ++ socket_address->set_address("1.2.3.4"); ++ socket_address->set_port_value(port); ++ endpoint->mutable_health_check_config()->set_disable_active_health_check(disable_hc); ++ if (disable_hc) { ++ if (healthy) { ++ lb_endpoint->set_health_status(envoy::config::core::v3::HEALTHY); ++ } else { ++ lb_endpoint->set_health_status(envoy::config::core::v3::TIMEOUT); ++ } ++ } ++ }; ++ ++ // First endpoint with disabled active HC. ++ add_endpoint(80, true, false); ++ // Second endpoint with enabled active HC. ++ add_endpoint(81, false, false); ++ doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); ++ { ++ auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(hosts.size(), 2); ++ ++ // The endpoint with disabled active health check should not be set FAILED_ACTIVE_HC ++ // and PENDING_ACTIVE_HC at beginning. ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); ++ EXPECT_TRUE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_EDS_HEALTH)); ++ EXPECT_TRUE(hosts[1]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ ++ EXPECT_EQ(Host::Health::Unhealthy, hosts[0]->coarseHealth()); ++ EXPECT_EQ(Host::Health::Unhealthy, hosts[1]->coarseHealth()); ++ ++ // Remove the pending HC & mark the second host as healthy. ++ // This is normally done by the health checker. ++ hosts[1]->healthFlagClear(Host::HealthFlag::PENDING_ACTIVE_HC); ++ hosts[1]->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); ++ ++ // After the active health check status is changed, run the callbacks to reload hosts. ++ health_checker->runCallbacks(hosts[1], HealthTransition::Changed); ++ ++ auto& hosts_reload = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(hosts_reload.size(), 2); ++ EXPECT_EQ(Host::Health::Healthy, hosts_reload[1]->coarseHealth()); ++ EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ } ++ ++ // Now mark the port 80 endpoint as healthy through EDS, no change for the other one. ++ cluster_load_assignment.clear_endpoints(); ++ add_endpoint(80, true, true); ++ add_endpoint(81, false, false); ++ doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); ++ HostSharedPtr removed_host; ++ { ++ auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(hosts.size(), 2); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_EDS_HEALTH)); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); ++ ++ removed_host = hosts[1]; ++ EXPECT_FALSE(hosts[1]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); ++ EXPECT_EQ(2UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ } ++ ++ // Disable active health check for both endpoints. ++ cluster_load_assignment.clear_endpoints(); ++ add_endpoint(80, true, true); ++ add_endpoint(81, true, true); ++ doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); ++ { ++ // Both hosts should be present, and both should not be PENDING_DYNAMIC_REMOVAL. ++ auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(hosts.size(), 2); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_EDS_HEALTH)); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); ++ ++ // Verify that we have a new host. The host is removed even it is active ++ // healthy when the active hc flag is changed. ++ EXPECT_EQ(removed_host->address()->asString(), hosts[1]->address()->asString()); ++ EXPECT_NE(removed_host, hosts[1]); ++ EXPECT_FALSE(hosts[1]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); ++ EXPECT_EQ(2UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ } ++ ++ // Enable the active health check for the port 80 endpoint. ++ cluster_load_assignment.clear_endpoints(); ++ add_endpoint(80, false, true); ++ doOnConfigUpdateVerifyNoThrow(cluster_load_assignment); ++ { ++ auto& hosts = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(hosts.size(), 1); ++ EXPECT_TRUE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ } ++} ++ + // Validate that onConfigUpdate() removes endpoints that are marked as healthy + // when configured to drain on host removal. + TEST_F(EdsTest, EndpointRemovalClusterDrainOnHostRemoval) { +diff --git a/test/common/upstream/hds_test.cc b/test/common/upstream/hds_test.cc +index c831352432..6879ee2640 100644 +--- a/test/common/upstream/hds_test.cc ++++ b/test/common/upstream/hds_test.cc +@@ -153,7 +153,8 @@ protected: + // Creates a HealthCheckSpecifier message that contains several clusters, endpoints, localities, + // with only one health check type. + std::unique_ptr +- createComplexSpecifier(uint32_t n_clusters, uint32_t n_localities, uint32_t n_endpoints) { ++ createComplexSpecifier(uint32_t n_clusters, uint32_t n_localities, uint32_t n_endpoints, ++ bool disable_hc = false) { + // Final specifier to return. + std::unique_ptr msg = + std::make_unique(); +@@ -188,11 +189,13 @@ protected: +  + // add some endpoints to the locality group with iterative naming for verification. + for (uint32_t endpoint_num = 0; endpoint_num < n_endpoints; endpoint_num++) { +- auto* socket_address = +- locality_endpoints->add_endpoints()->mutable_address()->mutable_socket_address(); ++ auto* endpoint = locality_endpoints->add_endpoints(); ++ ++ auto* socket_address = endpoint->mutable_address()->mutable_socket_address(); + socket_address->set_address( + absl::StrCat("127.", cluster_num, ".", loc_num, ".", endpoint_num)); + socket_address->set_port_value(1234); ++ endpoint->mutable_health_check_config()->set_disable_active_health_check(disable_hc); + } + } + } +@@ -893,6 +896,42 @@ TEST_F(HdsTest, TestUpdateEndpoints) { + checkHdsCounters(3, 0, 0, 3); + } +  ++// Skip the endpoints with disabled active health check during message processing. ++TEST_F(HdsTest, TestUpdateEndpointsWithActiveHCflag) { ++ EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); ++ EXPECT_CALL(async_stream_, sendMessageRaw_(_, _)); ++ createHdsDelegate(); ++ ++ // Create Message, and later add/remove endpoints from the second cluster. ++ message.reset(createSimpleMessage()); ++ message->MergeFrom(*createComplexSpecifier(1, 1, 2)); ++ ++ // Create a new active connection on request, setting its status to connected ++ // to mock a found endpoint. ++ expectCreateClientConnection(); ++ ++ EXPECT_CALL(*server_response_timer_, enableTimer(_, _)).Times(AtLeast(1)); ++ EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); ++ EXPECT_CALL(test_factory_, createClusterInfo(_)).WillRepeatedly(Return(cluster_info_)); ++ EXPECT_CALL(server_context_.dispatcher_, deferredDelete_(_)).Times(AtLeast(1)); ++ // Process message ++ hds_delegate_->onReceiveMessage(std::move(message)); ++ hds_delegate_->sendResponse(); ++ ++ // Save list of hosts/endpoints for comparison later. ++ auto original_hosts = hds_delegate_->hdsClusters()[1]->hosts(); ++ ASSERT_EQ(original_hosts.size(), 2); ++ ++ // Ignoring the endpoints with disabled active health check. ++ message.reset(createSimpleMessage()); ++ message->MergeFrom(*createComplexSpecifier(1, 1, 2, true)); ++ hds_delegate_->onReceiveMessage(std::move(message)); ++ ++ // Get the new clusters list from HDS. ++ auto new_hosts = hds_delegate_->hdsClusters()[1]->hosts(); ++ ASSERT_EQ(new_hosts.size(), 0); ++} ++ + // Test adding, reusing, and removing health checks. + TEST_F(HdsTest, TestUpdateHealthCheckers) { + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); +diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc +index 06aecdb303..e845aceb08 100644 +--- a/test/common/upstream/health_check_fuzz.cc ++++ b/test/common/upstream/health_check_fuzz.cc +@@ -106,7 +106,7 @@ void HttpHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", *time_source)}; + if (input.upstream_cx_success()) { +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + } + expectSessionCreate(); + expectStreamCreate(0); +@@ -217,7 +217,7 @@ void TcpHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", *time_source)}; + if (input.upstream_cx_success()) { +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + } + expectSessionCreate(); + expectClientCreate(); +@@ -326,7 +326,7 @@ void GrpcHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", *time_source)}; + if (input.upstream_cx_success()) { +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + } + expectSessionCreate(); + ON_CALL(dispatcher_, createClientConnection_(_, _, _, _)) +diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc +index cccc700549..3fd5f68733 100644 +--- a/test/common/upstream/health_checker_impl_test.cc ++++ b/test/common/upstream/health_checker_impl_test.cc +@@ -858,7 +858,7 @@ TEST_F(HttpHealthCheckerImplTest, Success) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -881,7 +881,7 @@ TEST_F(HttpHealthCheckerImplTest, Degraded) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1010,7 +1010,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessIntervalJitterPercent) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1041,7 +1041,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithSpurious1xx) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1069,7 +1069,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithSpuriousMetadata) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1099,8 +1099,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHosts) { + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(cluster_->info_, "tcp://127.0.0.1:81", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1136,8 +1136,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHostSets) { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; + cluster_->prioritySet().getMockHostSet(1)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:81", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1170,7 +1170,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessExpectedResponseCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1193,7 +1193,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessExpectedResponseStringContainsCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1217,7 +1217,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessExpectedResponseHexStringContainsCheck) +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1240,7 +1240,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessExpectedResponseCheckBuffer) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1265,7 +1265,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessExpectedResponseCheckMaxBuffer) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1305,7 +1305,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessExpectedResponseCheckHttp2) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1330,7 +1330,7 @@ TEST_F(HttpHealthCheckerImplTest, FailExpectedResponseCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1356,7 +1356,7 @@ TEST_F(HttpHealthCheckerImplTest, FailStatusCheckWithExpectedResponseCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1381,7 +1381,7 @@ TEST_F(HttpHealthCheckerImplTest, ImmediateFailExpectedResponseCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1428,7 +1428,7 @@ TEST_F(HttpHealthCheckerImplTest, ZeroRetryInterval) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1487,7 +1487,7 @@ TEST_F(HttpHealthCheckerImplTest, TlsOptions) { + allocHealthChecker(yaml); + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1505,7 +1505,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1541,7 +1541,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServicePrefixPatternCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1577,7 +1577,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceExactPatternCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1613,7 +1613,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceRegexPatternCheck) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1657,7 +1657,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValueOnTheHos + EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = {test_host}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1703,7 +1703,7 @@ TEST_F(HttpHealthCheckerImplTest, + EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = {test_host}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1739,7 +1739,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1804,7 +1804,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", metadata, simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1869,7 +1869,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithoutUserAgent) { + std::string current_start_time; + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", metadata, simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1907,7 +1907,7 @@ TEST_F(HttpHealthCheckerImplTest, ServiceDoesNotMatchFail) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1938,7 +1938,7 @@ TEST_F(HttpHealthCheckerImplTest, ServicePatternDoesNotMatchFail) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1969,7 +1969,7 @@ TEST_F(HttpHealthCheckerImplTest, ServiceNotPresentInResponseFail) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -1997,7 +1997,7 @@ TEST_F(HttpHealthCheckerImplTest, ServiceCheckRuntimeOff) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -2024,7 +2024,7 @@ TEST_F(HttpHealthCheckerImplTest, ServiceCheckRuntimeOffWithStringPattern) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -2478,6 +2478,54 @@ TEST_F(HttpHealthCheckerImplTest, DynamicAddAndRemove) { + cluster_->prioritySet().getMockHostSet(0)->runCallbacks({}, removed); + } +  ++// Verify the removal when disable active health check for a host works. ++TEST_F(HttpHealthCheckerImplTest, DynamicRemoveDisableHC) { ++ setupNoServiceValidationHC(); ++ ++ expectSessionCreate(); ++ expectStreamCreate(0); ++ EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); ++ ++ envoy::config::endpoint::v3::Endpoint::HealthCheckConfig health_check_config; ++ health_check_config.set_disable_active_health_check(false); ++ auto enable_host = std::make_shared( ++ cluster_->info_, "test_host", Network::Utility::resolveUrl("tcp://127.0.0.1:80"), nullptr, 1, ++ envoy::config::core::v3::Locality(), health_check_config, 0, envoy::config::core::v3::UNKNOWN, ++ simTime()); ++ cluster_->prioritySet().getMockHostSet(0)->hosts_ = {enable_host}; ++ health_checker_->start(); ++ EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); ++ ++ health_check_config.set_disable_active_health_check(true); ++ auto disable_host = std::make_shared( ++ cluster_->info_, "test_host", Network::Utility::resolveUrl("tcp://127.0.0.1:80"), nullptr, 1, ++ envoy::config::core::v3::Locality(), health_check_config, 0, envoy::config::core::v3::UNKNOWN, ++ simTime()); ++ cluster_->prioritySet().getMockHostSet(0)->hosts_ = {disable_host}; ++ EXPECT_CALL(*test_sessions_[0]->client_connection_, close(_)); ++ cluster_->prioritySet().runUpdateCallbacks(0, {disable_host}, {enable_host}); ++} ++ ++// Verify a session in health checker is not created when disable health check. ++TEST_F(HttpHealthCheckerImplTest, AddDisableHC) { ++ setupNoServiceValidationHC(); ++ ++ TestSessionPtr new_test_session(new TestSession()); ++ test_sessions_.emplace_back(std::move(new_test_session)); ++ EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)).Times(0); ++ EXPECT_CALL(*health_checker_, createCodecClient_(_)).Times(0); ++ ++ envoy::config::endpoint::v3::Endpoint::HealthCheckConfig health_check_config; ++ health_check_config.set_disable_active_health_check(true); ++ auto disable_host = std::make_shared( ++ cluster_->info_, "test_host", Network::Utility::resolveUrl("tcp://127.0.0.1:80"), nullptr, 1, ++ envoy::config::core::v3::Locality(), health_check_config, 0, envoy::config::core::v3::UNKNOWN, ++ simTime()); ++ cluster_->prioritySet().getMockHostSet(0)->hosts_ = {disable_host}; ++ health_checker_->start(); ++ EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)).Times(0); ++} ++ + TEST_F(HttpHealthCheckerImplTest, ConnectionClose) { + setupNoServiceValidationHC(); + EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); +@@ -2538,7 +2586,7 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000), _)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); + respond(0, "200", false); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); +  + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); + // Needed after a response is sent. +@@ -2877,7 +2925,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAltPort) { + // Prepares a host with its designated health check port. + const HostWithHealthCheckMap hosts{{"127.0.0.1:80", makeHealthCheckConfig(8000)}}; + appendTestHosts(cluster_, hosts); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(hosts); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -2910,8 +2958,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHostsAndAltPort) { + const HostWithHealthCheckMap hosts = {{"127.0.0.1:80", makeHealthCheckConfig(8000)}, + {"127.0.0.1:81", makeHealthCheckConfig(8001)}}; + appendTestHosts(cluster_, hosts); +- cluster_->info_->stats().upstream_cx_total_.inc(); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(hosts); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -2951,7 +2999,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAltAddress) { + const HostWithHealthCheckMap hosts{ + {"127.0.0.1:80", makeHealthCheckConfigAltAddress("127.0.0.2", 8000)}}; + appendTestHosts(cluster_, hosts); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(hosts); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -2985,8 +3033,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHostsAndAltAddress) { + {"127.0.0.1:80", makeHealthCheckConfigAltAddress("127.0.0.2", 8000)}, + {"127.0.0.2:81", makeHealthCheckConfigAltAddress("127.0.0.2", 8000)}}; + appendTestHosts(cluster_, hosts); +- cluster_->info_->stats().upstream_cx_total_.inc(); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(hosts); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3082,7 +3130,7 @@ TEST_F(HttpHealthCheckerImplTest, TransportSocketMatchCriteria) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3123,7 +3171,7 @@ TEST_F(HttpHealthCheckerImplTest, NoTransportSocketMatchCriteria) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3140,7 +3188,7 @@ TEST_F(HttpHealthCheckerImplTest, GoAwayErrorProbeInProgress) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3188,7 +3236,7 @@ TEST_F(HttpHealthCheckerImplTest, GoAwayProbeInProgress) { + .WillRepeatedly(Return(false)); + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); +  + expectSessionCreate(); + expectStreamCreate(0); +@@ -3487,7 +3535,7 @@ TEST_F(HttpHealthCheckerImplTest, ServiceNameMatch) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3523,7 +3571,7 @@ TEST_F(HttpHealthCheckerImplTest, ServiceNameMismatch) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3549,7 +3597,7 @@ TEST_F(HttpHealthCheckerImplTest, DefaultMethodGet) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -3578,7 +3626,7 @@ TEST_F(HttpHealthCheckerImplTest, MethodHead) { +  + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); +@@ -4818,7 +4866,7 @@ public: + // performed during test case (but possibly on many hosts). + void expectHealthchecks(HealthTransition host_changed_state, size_t num_healthchecks) { + for (size_t i = 0; i < num_healthchecks; i++) { +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectHealthcheckStart(i); + } +@@ -4921,7 +4969,7 @@ public: +  + void runHealthCheck(std::string expected_host) { +  +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); +  + expectSessionCreate(); + expectHealthcheckStart(0); +@@ -5071,7 +5119,7 @@ TEST_F(GrpcHealthCheckerImplTest, SuccessWithAdditionalHeaders) { + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", metadata, simTime())}; +  +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); +  + expectSessionCreate(); + expectHealthcheckStart(0); +@@ -5543,7 +5591,7 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000), _)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); + respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); +- cluster_->info_->stats().upstream_cx_total_.inc(); ++ cluster_->info_->trafficStats().upstream_cx_total_.inc(); +  + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); + // Needed after a response is sent. +diff --git a/test/common/upstream/load_balancer_benchmark.cc b/test/common/upstream/load_balancer_benchmark.cc +index 6a855bae9a..bf7feac8fe 100644 +--- a/test/common/upstream/load_balancer_benchmark.cc ++++ b/test/common/upstream/load_balancer_benchmark.cc +@@ -66,8 +66,8 @@ public: + PrioritySetImpl priority_set_; + PrioritySetImpl local_priority_set_; + Stats::IsolatedStoreImpl stats_store_; +- ClusterStatNames stat_names_{stats_store_.symbolTable()}; +- ClusterStats stats_{ClusterInfoImpl::generateStats(stats_store_, stat_names_)}; ++ ClusterLbStatNames stat_names_{stats_store_.symbolTable()}; ++ ClusterLbStats stats_{stat_names_, stats_store_}; + NiceMock runtime_; + Random::RandomGeneratorImpl random_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; +diff --git a/test/common/upstream/load_balancer_fuzz_base.h b/test/common/upstream/load_balancer_fuzz_base.h +index 727da68904..f8a33319e0 100644 +--- a/test/common/upstream/load_balancer_fuzz_base.h ++++ b/test/common/upstream/load_balancer_fuzz_base.h +@@ -21,8 +21,7 @@ namespace Upstream { + class LoadBalancerFuzzBase { + public: + LoadBalancerFuzzBase() +- : stat_names_(stats_store_.symbolTable()), +- stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)){}; ++ : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, stats_store_){}; +  + // Initializes load balancer components shared amongst every load balancer, random_, and + // priority_set_ +@@ -42,8 +41,8 @@ public: + // These public objects shared amongst all types of load balancers will be used to construct load + // balancers in specific load balancer fuzz classes + Stats::IsolatedStoreImpl stats_store_; +- ClusterStatNames stat_names_; +- ClusterStats stats_; ++ ClusterLbStatNames stat_names_; ++ ClusterLbStats stats_; + NiceMock runtime_; + Random::PsuedoRandomGenerator64 random_; + NiceMock priority_set_; +diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc +index d657bcd74f..b503014a30 100644 +--- a/test/common/upstream/load_balancer_impl_test.cc ++++ b/test/common/upstream/load_balancer_impl_test.cc +@@ -54,10 +54,11 @@ public: + class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { + public: + TestZoneAwareLoadBalancer( +- const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, ++ const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +- : ZoneAwareLoadBalancerBase(priority_set, nullptr, stats, runtime, random, common_config) {} ++ : ZoneAwareLoadBalancerBase(priority_set, nullptr, lb_stats, runtime, random, common_config) { ++ } + void runInvalidLocalitySourceType() { + localitySourceType(static_cast(123)); + } +@@ -76,14 +77,13 @@ protected: + MockHostSet& hostSet() { return GetParam() ? host_set_ : failover_host_set_; } +  + LoadBalancerTestBase() +- : stat_names_(stats_store_.symbolTable()), +- stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)) { ++ : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, stats_store_) { + least_request_lb_config_.mutable_choice_count()->set_value(2); + } +  + Stats::IsolatedStoreImpl stats_store_; +- ClusterStatNames stat_names_; +- ClusterStats stats_; ++ ClusterLbStatNames stat_names_; ++ ClusterLbStats stats_; + NiceMock runtime_; + NiceMock random_; + NiceMock priority_set_; +@@ -97,10 +97,10 @@ protected: +  + class TestLb : public LoadBalancerBase { + public: +- TestLb(const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, ++ TestLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +- : LoadBalancerBase(priority_set, stats, runtime, random, common_config) {} ++ : LoadBalancerBase(priority_set, lb_stats, runtime, random, common_config) {} + using LoadBalancerBase::chooseHostSet; + using LoadBalancerBase::isInPanic; + using LoadBalancerBase::percentageDegradedLoad; +@@ -581,10 +581,11 @@ TEST_P(LoadBalancerBaseTest, BoundaryConditions) { +  + class TestZoneAwareLb : public ZoneAwareLoadBalancerBase { + public: +- TestZoneAwareLb(const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, +- Random::RandomGenerator& random, ++ TestZoneAwareLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, ++ Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) +- : ZoneAwareLoadBalancerBase(priority_set, nullptr, stats, runtime, random, common_config) {} ++ : ZoneAwareLoadBalancerBase(priority_set, nullptr, lb_stats, runtime, random, common_config) { ++ } +  + HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { + return choose_host_once_host_; +@@ -1959,14 +1960,12 @@ TEST_P(LeastRequestLoadBalancerTest, SingleHost) { + // Host weight is 1. + { + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); +- stats_.max_host_weight_.set(1UL); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + } +  + // Host weight is 100. + { + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); +- stats_.max_host_weight_.set(100UL); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + } +  +@@ -1991,7 +1990,6 @@ TEST_P(LeastRequestLoadBalancerTest, SingleHost) { + TEST_P(LeastRequestLoadBalancerTest, Normal) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; +- stats_.max_host_weight_.set(1UL); + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. +  +@@ -2011,7 +2009,6 @@ TEST_P(LeastRequestLoadBalancerTest, PNC) { + makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime())}; +- stats_.max_host_weight_.set(1UL); + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. +  +@@ -2058,7 +2055,6 @@ TEST_P(LeastRequestLoadBalancerTest, PNC) { + TEST_P(LeastRequestLoadBalancerTest, WeightImbalance) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), 1), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), 2)}; +- stats_.max_host_weight_.set(2UL); +  + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. +@@ -2183,7 +2179,6 @@ TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceWithCustomActiveRequestBias) + TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceCallbacks) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), 1), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), 2)}; +- stats_.max_host_weight_.set(2UL); +  + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. +diff --git a/test/common/upstream/load_balancer_simulation_test.cc b/test/common/upstream/load_balancer_simulation_test.cc +index 22d081562f..600cff25d4 100644 +--- a/test/common/upstream/load_balancer_simulation_test.cc ++++ b/test/common/upstream/load_balancer_simulation_test.cc +@@ -70,16 +70,15 @@ TEST(DISABLED_LeastRequestLoadBalancerWeightTest, Weight) { + {}, hosts, {}, absl::nullopt); +  + Stats::IsolatedStoreImpl stats_store; +- ClusterStatNames stat_names(stats_store.symbolTable()); +- ClusterStats stats{ClusterInfoImpl::generateStats(stats_store, stat_names)}; +- stats.max_host_weight_.set(weight); ++ ClusterLbStatNames stat_names(stats_store.symbolTable()); ++ ClusterLbStats lb_stats{stat_names, stats_store}; + NiceMock runtime; + auto time_source = std::make_unique>(); + Random::RandomGeneratorImpl random; + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig least_request_lb_config; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config; + LeastRequestLoadBalancer lb_{ +- priority_set, nullptr, stats, runtime, random, common_config, least_request_lb_config, ++ priority_set, nullptr, lb_stats, runtime, random, common_config, least_request_lb_config, + *time_source}; +  + absl::node_hash_map host_hits; +@@ -105,11 +104,10 @@ TEST(DISABLED_LeastRequestLoadBalancerWeightTest, Weight) { + /** + * This test is for simulation only and should not be run as part of unit tests. + */ +-class DISABLED_SimulationTest : public testing::Test { ++class DISABLED_SimulationTest : public testing::Test { // NOLINT(readability-identifier-naming) + public: + DISABLED_SimulationTest() +- : stat_names_(stats_store_.symbolTable()), +- stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)) { ++ : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, stats_store_) { + ON_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50U)) + .WillByDefault(Return(50U)); + ON_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) +@@ -247,8 +245,8 @@ public: + NiceMock time_source_; + Random::RandomGeneratorImpl random_; + Stats::IsolatedStoreImpl stats_store_; +- ClusterStatNames stat_names_; +- ClusterStats stats_; ++ ClusterLbStatNames stat_names_; ++ ClusterLbStats stats_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + }; +  +diff --git a/test/common/upstream/maglev_lb_test.cc b/test/common/upstream/maglev_lb_test.cc +index b9aef9fabf..a668aafaf7 100644 +--- a/test/common/upstream/maglev_lb_test.cc ++++ b/test/common/upstream/maglev_lb_test.cc +@@ -45,8 +45,7 @@ public: + class MaglevLoadBalancerTest : public Event::TestUsingSimulatedTime, public testing::Test { + public: + MaglevLoadBalancerTest() +- : stat_names_(stats_store_.symbolTable()), +- stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)) {} ++ : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, stats_store_) {} +  + void createLb() { + lb_ = std::make_unique(priority_set_, stats_, stats_store_, runtime_, +@@ -65,8 +64,8 @@ public: + MockHostSet& host_set_ = *priority_set_.getMockHostSet(0); + std::shared_ptr info_{new NiceMock()}; + Stats::IsolatedStoreImpl stats_store_; +- ClusterStatNames stat_names_; +- ClusterStats stats_; ++ ClusterLbStatNames stat_names_; ++ ClusterLbStats stats_; + absl::optional config_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + NiceMock runtime_; +diff --git a/test/common/upstream/ring_hash_lb_test.cc b/test/common/upstream/ring_hash_lb_test.cc +index fa76bdf721..84a24104c1 100644 +--- a/test/common/upstream/ring_hash_lb_test.cc ++++ b/test/common/upstream/ring_hash_lb_test.cc +@@ -58,8 +58,7 @@ class RingHashLoadBalancerTest : public Event::TestUsingSimulatedTime, + public testing::TestWithParam { + public: + RingHashLoadBalancerTest() +- : stat_names_(stats_store_.symbolTable()), +- stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)) {} ++ : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, stats_store_) {} +  + void init() { + lb_ = std::make_unique(priority_set_, stats_, stats_store_, runtime_, +@@ -76,8 +75,8 @@ public: + MockHostSet& failover_host_set_ = *priority_set_.getMockHostSet(1); + std::shared_ptr info_{new NiceMock()}; + Stats::IsolatedStoreImpl stats_store_; +- ClusterStatNames stat_names_; +- ClusterStats stats_; ++ ClusterLbStatNames stat_names_; ++ ClusterLbStats stats_; + absl::optional config_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + NiceMock runtime_; +diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc +index 68c24dfda7..6011423dd3 100644 +--- a/test/common/upstream/subset_lb_test.cc ++++ b/test/common/upstream/subset_lb_test.cc +@@ -170,8 +170,7 @@ class SubsetLoadBalancerTest : public Event::TestUsingSimulatedTime, + public: + SubsetLoadBalancerTest() + : scope_(stats_store_.createScope("testprefix")), stat_names_(stats_store_.symbolTable()), +- stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)) { +- stats_.max_host_weight_.set(1UL); ++ stats_(stat_names_, stats_store_) { + least_request_lb_config_.mutable_choice_count()->set_value(2); + } +  +@@ -528,8 +527,8 @@ public: + NiceMock random_; + Stats::IsolatedStoreImpl stats_store_; + Stats::ScopeSharedPtr scope_; +- ClusterStatNames stat_names_; +- ClusterStats stats_; ++ ClusterLbStatNames stat_names_; ++ ClusterLbStats stats_; + PrioritySetImpl local_priority_set_; + HostVectorSharedPtr local_hosts_; + HostsPerLocalitySharedPtr local_hosts_per_locality_; +diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc +index 837a8e3c13..0d2a0a9beb 100644 +--- a/test/common/upstream/upstream_impl_test.cc ++++ b/test/common/upstream/upstream_impl_test.cc +@@ -420,7 +420,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { + EXPECT_EQ(1U, cluster.info()->resourceManager(ResourcePriority::Default).maxConnectionsPerHost()); + EXPECT_EQ(990U, cluster.info()->resourceManager(ResourcePriority::High).maxConnectionsPerHost()); +  +- cluster.info()->stats().upstream_rq_total_.inc(); ++ cluster.info()->trafficStats().upstream_rq_total_.inc(); + EXPECT_EQ(1UL, stats_.counter("cluster.name.upstream_rq_total").value()); +  + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.maintenance_mode.name", 0)); +@@ -673,6 +673,73 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalAfterHcFail) { + } + } +  ++TEST_F(StrictDnsClusterImplTest, HostUpdateWithDisabledACEndpoint) { ++ const std::string yaml = R"EOF( ++ name: name ++ connect_timeout: 0.25s ++ type: STRICT_DNS ++ lb_policy: ROUND_ROBIN ++ load_assignment: ++ endpoints: ++ - lb_endpoints: ++ - endpoint: ++ address: ++ socket_address: ++ address: foo.bar.com ++ port_value: 443 ++ health_check_config: ++ disable_active_health_check: true ++ )EOF"; ++ ++ ResolverData resolver(*dns_resolver_, server_context_.dispatcher_); ++ envoy::config::cluster::v3::Cluster cluster_config = parseClusterFromV3Yaml(yaml); ++ Envoy::Stats::ScopeSharedPtr scope = stats_.createScope(fmt::format( ++ "cluster.{}.", cluster_config.alt_stat_name().empty() ? cluster_config.name() ++ : cluster_config.alt_stat_name())); ++ Envoy::Server::Configuration::TransportSocketFactoryContextImpl factory_context( ++ server_context_, ssl_context_manager_, *scope, server_context_.cluster_manager_, stats_, ++ validation_visitor_); ++ StrictDnsClusterImpl cluster(server_context_, cluster_config, runtime_, dns_resolver_, ++ factory_context, std::move(scope), false); ++ std::shared_ptr health_checker(new MockHealthChecker()); ++ EXPECT_CALL(*health_checker, start()); ++ EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); ++ cluster.setHealthChecker(health_checker); ++ ReadyWatcher initialized; ++ cluster.initialize([&initialized]() { initialized.ready(); }); ++ EXPECT_CALL(initialized, ready()); ++ ++ EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); ++ EXPECT_CALL(*resolver.timer_, enableTimer(_, _)).Times(2); ++ resolver.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, ++ TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); ++ ++ { ++ const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(2UL, hosts.size()); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_FALSE(hosts[1]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ ++ EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ EXPECT_EQ(2UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); ++ } ++ ++ // Re-resolve the DNS name with only one record, we should have 1 host. ++ resolver.dns_callback_(Network::DnsResolver::ResolutionStatus::Success, ++ TestUtility::makeDnsResponse({"127.0.0.1"})); ++ ++ { ++ const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(1UL, hosts.size()); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::PENDING_DYNAMIC_REMOVAL)); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); ++ } ++} ++ + TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { + // gmock matches in LIFO order which is why these are swapped. + ResolverData resolver3(*dns_resolver_, server_context_.dispatcher_); +@@ -770,7 +837,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { + EXPECT_EQ(3U, cluster.info()->maxRequestsPerConnection()); + EXPECT_EQ(0U, cluster.info()->http2Options().hpack_table_size().value()); +  +- cluster.info()->stats().upstream_rq_total_.inc(); ++ cluster.info()->trafficStats().upstream_rq_total_.inc(); + EXPECT_EQ(1UL, stats_.counter("cluster.name.upstream_rq_total").value()); +  + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.maintenance_mode.name", 0)); +@@ -1507,6 +1574,16 @@ TEST_F(HostImplTest, HealthStatus) { + EXPECT_EQ(Host::HealthStatus::DEGRADED, host->healthStatus()); + } +  ++TEST_F(HostImplTest, SkipActiveHealthCheckFlag) { ++ MockClusterMockPrioritySet cluster; ++ HostSharedPtr host = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", simTime(), 1); ++ ++ // To begin with, the default setting is false. ++ EXPECT_EQ(false, host->disableActiveHealthCheck()); ++ host->setDisableActiveHealthCheck(true); ++ EXPECT_EQ(true, host->disableActiveHealthCheck()); ++} ++ + // Test that it's not possible to do a HostDescriptionImpl with a unix + // domain socket host and a health check config with non-zero port. + // This is a regression test for oss-fuzz issue +@@ -1572,6 +1649,8 @@ TEST_F(StaticClusterImplTest, InitialHosts) { + cluster.initialize([] {}); +  + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); + EXPECT_EQ("", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_FALSE(cluster.info()->addedViaApi()); + } +@@ -1859,7 +1938,7 @@ TEST_F(StaticClusterImplTest, AltStatName) { + std::move(scope), false); + cluster.initialize([] {}); + // Increment a stat and verify it is emitted with alt_stat_name +- cluster.info()->stats().upstream_rq_total_.inc(); ++ cluster.info()->trafficStats().upstream_rq_total_.inc(); + EXPECT_EQ(1UL, stats_.counter("cluster.staticcluster_stats.upstream_rq_total").value()); + } +  +@@ -2018,7 +2097,7 @@ TEST_F(StaticClusterImplTest, OutlierDetector) { + cluster.initialize([] {}); +  + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(2UL, cluster.info()->stats().membership_healthy_.value()); ++ EXPECT_EQ(2UL, cluster.info()->endpointStats().membership_healthy_.value()); +  + // Set a single host as having failed and fire outlier detector callbacks. This should result + // in only a single healthy host. +@@ -2028,7 +2107,7 @@ TEST_F(StaticClusterImplTest, OutlierDetector) { + Host::HealthFlag::FAILED_OUTLIER_CHECK); + detector->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_healthy_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); + EXPECT_NE(cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts()[0], + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]); +  +@@ -2037,7 +2116,7 @@ TEST_F(StaticClusterImplTest, OutlierDetector) { + Host::HealthFlag::FAILED_OUTLIER_CHECK); + detector->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(2UL, cluster.info()->stats().membership_healthy_.value()); ++ EXPECT_EQ(2UL, cluster.info()->endpointStats().membership_healthy_.value()); + } +  + TEST_F(StaticClusterImplTest, HealthyStat) { +@@ -2082,8 +2161,8 @@ TEST_F(StaticClusterImplTest, HealthyStat) { +  + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->healthFlagClear( + Host::HealthFlag::FAILED_ACTIVE_HC); +@@ -2099,46 +2178,46 @@ TEST_F(StaticClusterImplTest, HealthyStat) { + Host::HealthFlag::FAILED_OUTLIER_CHECK); + outlier_detector->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->healthFlagSet( + Host::HealthFlag::FAILED_ACTIVE_HC); + health_checker->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0], + HealthTransition::Changed); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->healthFlagClear( + Host::HealthFlag::FAILED_OUTLIER_CHECK); + outlier_detector->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->healthFlagClear( + Host::HealthFlag::FAILED_ACTIVE_HC); + health_checker->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0], + HealthTransition::Changed); + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(2UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(2UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->healthFlagSet( + Host::HealthFlag::FAILED_OUTLIER_CHECK); + outlier_detector->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->healthFlagSet( + Host::HealthFlag::FAILED_ACTIVE_HC); + health_checker->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1], + HealthTransition::Changed); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->healthFlagSet( + Host::HealthFlag::DEGRADED_ACTIVE_HC); +@@ -2148,8 +2227,8 @@ TEST_F(StaticClusterImplTest, HealthyStat) { + HealthTransition::Changed); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + // Mark the endpoint as unhealthy. This should decrement the degraded stat. + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->healthFlagSet( +@@ -2158,8 +2237,8 @@ TEST_F(StaticClusterImplTest, HealthyStat) { + HealthTransition::Changed); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + // Go back to degraded. + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->healthFlagClear( +@@ -2168,8 +2247,8 @@ TEST_F(StaticClusterImplTest, HealthyStat) { + HealthTransition::Changed); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_degraded_.value()); +  + // Then go healthy. + cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->healthFlagClear( +@@ -2178,8 +2257,72 @@ TEST_F(StaticClusterImplTest, HealthyStat) { + HealthTransition::Changed); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->degradedHosts().size()); +- EXPECT_EQ(1UL, cluster.info()->stats().membership_healthy_.value()); +- EXPECT_EQ(0UL, cluster.info()->stats().membership_degraded_.value()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); ++} ++ ++TEST_F(StaticClusterImplTest, InitialHostsDisableHC) { ++ const std::string yaml = R"EOF( ++ name: staticcluster ++ connect_timeout: 0.25s ++ type: STATIC ++ lb_policy: ROUND_ROBIN ++ load_assignment: ++ endpoints: ++ - lb_endpoints: ++ - endpoint: ++ address: ++ socket_address: ++ address: 10.0.0.1 ++ port_value: 11001 ++ health_check_config: ++ disable_active_health_check: true ++ - endpoint: ++ address: ++ socket_address: ++ address: 10.0.0.1 ++ port_value: 11002 ++ )EOF"; ++ ++ envoy::config::cluster::v3::Cluster cluster_config = parseClusterFromV3Yaml(yaml); ++ Envoy::Stats::ScopeSharedPtr scope = stats_.createScope(fmt::format( ++ "cluster.{}.", cluster_config.alt_stat_name().empty() ? cluster_config.name() ++ : cluster_config.alt_stat_name())); ++ Envoy::Server::Configuration::TransportSocketFactoryContextImpl factory_context( ++ server_context_, ssl_context_manager_, *scope, server_context_.cluster_manager_, stats_, ++ validation_visitor_); ++ StaticClusterImpl cluster(server_context_, cluster_config, runtime_, factory_context, ++ std::move(scope), false); ++ ++ Outlier::MockDetector* outlier_detector = new NiceMock(); ++ cluster.setOutlierDetector(Outlier::DetectorSharedPtr{outlier_detector}); ++ ++ std::shared_ptr health_checker(new NiceMock()); ++ cluster.setHealthChecker(health_checker); ++ ++ ReadyWatcher initialized; ++ cluster.initialize([&initialized] { initialized.ready(); }); ++ ++ // The endpoint with disabled active health check should not be set FAILED_ACTIVE_HC ++ // at beginning. ++ const auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); ++ EXPECT_EQ(2UL, hosts.size()); ++ EXPECT_FALSE(hosts[0]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ EXPECT_TRUE(hosts[1]->healthFlagGet(Host::HealthFlag::FAILED_ACTIVE_HC)); ++ ++ // The endpoint with disabled active health check is considered healthy. ++ EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); ++ EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); ++ EXPECT_EQ(1UL, cluster.info()->endpointStats().membership_healthy_.value()); ++ EXPECT_EQ(0UL, cluster.info()->endpointStats().membership_degraded_.value()); ++ ++ // Perform a health check for the second host, and then the initialization is finished. ++ EXPECT_CALL(initialized, ready()); ++ cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->healthFlagClear( ++ Host::HealthFlag::FAILED_ACTIVE_HC); ++ health_checker->runCallbacks(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0], ++ HealthTransition::Changed); ++ EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + } +  + TEST_F(StaticClusterImplTest, UrlConfig) { +diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc +index 73b3890089..e9ed21b670 100644 +--- a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc ++++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc +@@ -48,7 +48,6 @@ void ZoneAwareLoadBalancerFuzzBase::setupZoneAwareLoadBalancingSpecificLogic() { + // Having 3 possible weights, 1, 2, and 3 to provide the state space at least some variation + // in regards to weights, which do affect the load balancing algorithm. Cap the amount of + // weights at 3 for simplicity's sake +- stats_.max_host_weight_.set(3UL); + addWeightsToHosts(); + } +  +diff --git a/test/extensions/clusters/aggregate/cluster_test.cc b/test/extensions/clusters/aggregate/cluster_test.cc +index 0a9845b7a8..ab83624bb6 100644 +--- a/test/extensions/clusters/aggregate/cluster_test.cc ++++ b/test/extensions/clusters/aggregate/cluster_test.cc +@@ -138,8 +138,8 @@ public: + Upstream::ThreadAwareLoadBalancerPtr thread_aware_lb_; + Upstream::LoadBalancerFactorySharedPtr lb_factory_; + Upstream::LoadBalancerPtr lb_; +- Upstream::ClusterStatNames stat_names_; +- Upstream::ClusterStats stats_; ++ Upstream::ClusterTrafficStatNames stat_names_; ++ Upstream::ClusterTrafficStats stats_; + std::shared_ptr primary_info_{ + new NiceMock()}; + std::shared_ptr secondary_info_{ +diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc +index 7fea1b0cd1..630da461e1 100644 +--- a/test/extensions/clusters/redis/redis_cluster_test.cc ++++ b/test/extensions/clusters/redis/redis_cluster_test.cc +@@ -791,7 +791,7 @@ TEST_F(RedisClusterTest, AddressAsHostname) { + EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)); + expectClusterSlotResponse(singleSlotPrimaryReplica("primary.com", "replica.org", 22120)); + expectHealthyHosts(std::list({"127.0.1.1:22120", "127.0.1.2:22120"})); +- EXPECT_EQ(0U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(0U, cluster_->info()->configUpdateStats().update_failure_.value()); +  + // 2. Single slot with just the primary hostname + expectRedisResolve(true); +@@ -802,7 +802,7 @@ TEST_F(RedisClusterTest, AddressAsHostname) { + EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)); + expectClusterSlotResponse(singleSlotPrimary("primary.com", 22120)); + expectHealthyHosts(std::list({"127.0.1.1:22120"})); +- EXPECT_EQ(0U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(0U, cluster_->info()->configUpdateStats().update_failure_.value()); +  + // 2. Single slot with just the primary IP address and replica hostname + expectRedisResolve(); +@@ -813,7 +813,7 @@ TEST_F(RedisClusterTest, AddressAsHostname) { + EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)); + expectClusterSlotResponse(singleSlotPrimaryReplica("127.0.1.1", "replica.org", 22120)); + expectHealthyHosts(std::list({"127.0.1.1:22120", "127.0.1.2:22120"})); +- EXPECT_EQ(0U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(0U, cluster_->info()->configUpdateStats().update_failure_.value()); + } +  + TEST_F(RedisClusterTest, AddressAsHostnameParallelResolution) { +@@ -845,7 +845,7 @@ TEST_F(RedisClusterTest, AddressAsHostnameParallelResolution) { + "127.0.1.1:22120", + "127.0.1.2:22120", + })); +- EXPECT_EQ(0U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(0U, cluster_->info()->configUpdateStats().update_failure_.value()); + } +  + TEST_F(RedisClusterTest, AddressAsHostnameFailure) { +@@ -875,7 +875,7 @@ TEST_F(RedisClusterTest, AddressAsHostnameFailure) { + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + expectHealthyHosts(std::list({"127.0.1.1:22120"})); +- EXPECT_EQ(1UL, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(1UL, cluster_->info()->configUpdateStats().update_failure_.value()); +  + // 2. Primary resolution fails, so replica resolution is not even called. + // Expect cluster slot update to be successful, with just one healthy host, and failure counter to +@@ -894,7 +894,7 @@ TEST_F(RedisClusterTest, AddressAsHostnameFailure) { + // healthy hosts is same as before, but failure count increases by 1 + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(2UL, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(2UL, cluster_->info()->configUpdateStats().update_failure_.value()); + } +  + TEST_F(RedisClusterTest, AddressAsHostnamePartialReplicaFailure) { +@@ -940,7 +940,7 @@ TEST_F(RedisClusterTest, EmptyDnsResponse) { +  + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1U, cluster_->info()->stats().update_empty_.value()); ++ EXPECT_EQ(1U, cluster_->info()->configUpdateStats().update_empty_.value()); +  + // Does not recreate the timer on subsequent DNS resolve calls. + EXPECT_CALL(*dns_timer, enableTimer(_, _)); +@@ -949,7 +949,7 @@ TEST_F(RedisClusterTest, EmptyDnsResponse) { +  + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(2U, cluster_->info()->stats().update_empty_.value()); ++ EXPECT_EQ(2U, cluster_->info()->configUpdateStats().update_empty_.value()); + } +  + TEST_F(RedisClusterTest, FailedDnsResponse) { +@@ -965,7 +965,7 @@ TEST_F(RedisClusterTest, FailedDnsResponse) { +  + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(0U, cluster_->info()->stats().update_empty_.value()); ++ EXPECT_EQ(0U, cluster_->info()->configUpdateStats().update_empty_.value()); +  + // Does not recreate the timer on subsequent DNS resolve calls. + EXPECT_CALL(*dns_timer, enableTimer(_, _)); +@@ -974,7 +974,7 @@ TEST_F(RedisClusterTest, FailedDnsResponse) { +  + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); +- EXPECT_EQ(1U, cluster_->info()->stats().update_empty_.value()); ++ EXPECT_EQ(1U, cluster_->info()->configUpdateStats().update_empty_.value()); + } +  + TEST_F(RedisClusterTest, Basic) { +@@ -1020,8 +1020,8 @@ TEST_F(RedisClusterTest, RedisResolveFailure) { +  + // Initialization will wait til the redis cluster succeed. + expectClusterSlotFailure(); +- EXPECT_EQ(1U, cluster_->info()->stats().update_attempt_.value()); +- EXPECT_EQ(1U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(1U, cluster_->info()->configUpdateStats().update_attempt_.value()); ++ EXPECT_EQ(1U, cluster_->info()->configUpdateStats().update_failure_.value()); +  + expectRedisResolve(true); + resolve_timer_->invokeCallback(); +@@ -1036,8 +1036,8 @@ TEST_F(RedisClusterTest, RedisResolveFailure) { + resolve_timer_->invokeCallback(); + expectClusterSlotFailure(); + expectHealthyHosts(std::list({"127.0.0.1:22120", "127.0.0.2:22120"})); +- EXPECT_EQ(3U, cluster_->info()->stats().update_attempt_.value()); +- EXPECT_EQ(2U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(3U, cluster_->info()->configUpdateStats().update_attempt_.value()); ++ EXPECT_EQ(2U, cluster_->info()->configUpdateStats().update_failure_.value()); + } +  + TEST_F(RedisClusterTest, FactoryInitNotRedisClusterTypeFailure) { +@@ -1092,8 +1092,8 @@ TEST_F(RedisClusterTest, RedisErrorResponse) { +  + EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)).Times(0); + expectClusterSlotResponse(std::move(hello_world_response)); +- EXPECT_EQ(1U, cluster_->info()->stats().update_attempt_.value()); +- EXPECT_EQ(1U, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(1U, cluster_->info()->configUpdateStats().update_attempt_.value()); ++ EXPECT_EQ(1U, cluster_->info()->configUpdateStats().update_failure_.value()); +  + expectRedisResolve(); + resolve_timer_->invokeCallback(); +@@ -1118,9 +1118,9 @@ TEST_F(RedisClusterTest, RedisErrorResponse) { + } + expectClusterSlotResponse(createResponse(flags, no_replica)); + expectHealthyHosts(std::list({"127.0.0.1:22120"})); +- EXPECT_EQ(++update_attempt, cluster_->info()->stats().update_attempt_.value()); ++ EXPECT_EQ(++update_attempt, cluster_->info()->configUpdateStats().update_attempt_.value()); + if (!flags.all()) { +- EXPECT_EQ(++update_failure, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(++update_failure, cluster_->info()->configUpdateStats().update_failure_.value()); + } + } + } +@@ -1155,9 +1155,9 @@ TEST_F(RedisClusterTest, RedisReplicaErrorResponse) { + } + expectHealthyHosts(std::list({"127.0.0.1:22120"})); + expectClusterSlotResponse(createResponse(single_slot_primary, replica_flags)); +- EXPECT_EQ(++update_attempt, cluster_->info()->stats().update_attempt_.value()); ++ EXPECT_EQ(++update_attempt, cluster_->info()->configUpdateStats().update_attempt_.value()); + if (!(replica_flags.all() || replica_flags.none())) { +- EXPECT_EQ(++update_failure, cluster_->info()->stats().update_failure_.value()); ++ EXPECT_EQ(++update_failure, cluster_->info()->configUpdateStats().update_failure_.value()); + } + } + } +diff --git a/test/extensions/filters/common/rbac/BUILD b/test/extensions/filters/common/rbac/BUILD +index dc8ec26940..57d88f3e74 100644 +--- a/test/extensions/filters/common/rbac/BUILD ++++ b/test/extensions/filters/common/rbac/BUILD +@@ -17,6 +17,7 @@ envoy_extension_cc_test( + srcs = ["matchers_test.cc"], + extension_names = ["envoy.filters.http.rbac"], + deps = [ ++ "//source/common/stream_info:filter_state_lib", + "//source/extensions/filters/common/expr:evaluator_lib", + "//source/extensions/filters/common/rbac:matchers_lib", + "//test/mocks/network:network_mocks", +diff --git a/test/extensions/filters/common/rbac/matchers_test.cc b/test/extensions/filters/common/rbac/matchers_test.cc +index 7d96e355d9..6d9a582df7 100644 +--- a/test/extensions/filters/common/rbac/matchers_test.cc ++++ b/test/extensions/filters/common/rbac/matchers_test.cc +@@ -6,6 +6,7 @@ +  + #include "source/common/network/address_impl.h" + #include "source/common/network/utility.h" ++#include "source/common/stream_info/filter_state_impl.h" + #include "source/extensions/filters/common/expr/evaluator.h" + #include "source/extensions/filters/common/rbac/matchers.h" +  +@@ -517,6 +518,28 @@ TEST(PathMatcher, ValidPathInHeader) { + checkMatcher(PathMatcher(matcher), false, Envoy::Network::MockConnection(), headers); + } +  ++class TestObject : public StreamInfo::FilterState::Object { ++public: ++ absl::optional serializeAsString() const override { return "test.value"; } ++}; ++ ++TEST(FilterStateMatcher, FilterStateMatcher) { ++ Envoy::Network::MockConnection conn; ++ Envoy::Http::TestRequestHeaderMapImpl header; ++ NiceMock info; ++ StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::Connection); ++ EXPECT_CALL(Const(info), filterState()).WillRepeatedly(ReturnRef(filter_state)); ++ ++ envoy::type::matcher::v3::FilterStateMatcher matcher; ++ matcher.set_key("test.key"); ++ matcher.mutable_string_match()->set_prefix("test"); ++ ++ checkMatcher(FilterStateMatcher(matcher), false, conn, header, info); ++ filter_state.setData("test.key", std::make_shared(), ++ StreamInfo::FilterState::StateType::ReadOnly); ++ checkMatcher(FilterStateMatcher(matcher), true, conn, header, info); ++} ++ + } // namespace + } // namespace RBAC + } // namespace Common +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 d930d03d37..3d4d143313 100644 +--- a/test/extensions/filters/http/cache/cache_entry_utils_test.cc ++++ b/test/extensions/filters/http/cache/cache_entry_utils_test.cc +@@ -26,6 +26,81 @@ TEST(Coverage, CacheEntryStatusStream) { + EXPECT_EQ(stream.str(), "Ok"); + } +  ++TEST(CacheEntryUtils, ApplyHeaderUpdateReplacesMultiValues) { ++ Http::TestResponseHeaderMapImpl headers{ ++ {"test_header", "test_value"}, ++ {"second_header", "second_value"}, ++ {"second_header", "additional_value"}, ++ }; ++ Http::TestResponseHeaderMapImpl new_headers{ ++ {"second_header", "new_second_value"}, ++ }; ++ applyHeaderUpdate(new_headers, headers); ++ Http::TestResponseHeaderMapImpl expected{ ++ {"test_header", "test_value"}, ++ {"second_header", "new_second_value"}, ++ }; ++ EXPECT_THAT(&headers, HeaderMapEqualIgnoreOrder(&expected)); ++} ++ ++TEST(CacheEntryUtils, ApplyHeaderUpdateAppliesMultiValues) { ++ Http::TestResponseHeaderMapImpl headers{ ++ {"test_header", "test_value"}, ++ {"second_header", "second_value"}, ++ }; ++ Http::TestResponseHeaderMapImpl new_headers{ ++ {"second_header", "new_second_value"}, ++ {"second_header", "another_new_second_value"}, ++ }; ++ applyHeaderUpdate(new_headers, headers); ++ Http::TestResponseHeaderMapImpl expected{ ++ {"test_header", "test_value"}, ++ {"second_header", "new_second_value"}, ++ {"second_header", "another_new_second_value"}, ++ }; ++ EXPECT_THAT(&headers, HeaderMapEqualIgnoreOrder(&expected)); ++} ++ ++TEST(CacheEntryUtils, ApplyHeaderUpdateIgnoresIgnoredValues) { ++ Http::TestResponseHeaderMapImpl headers{ ++ {"test_header", "test_value"}, {"etag", "original_etag"}, {"content-length", "123456"}, ++ {"content-range", "654321"}, {"vary", "original_vary"}, ++ }; ++ Http::TestResponseHeaderMapImpl new_headers{ ++ {"etag", "updated_etag"}, ++ {"content-length", "999999"}, ++ {"content-range", "999999"}, ++ {"vary", "updated_vary"}, ++ }; ++ applyHeaderUpdate(new_headers, headers); ++ Http::TestResponseHeaderMapImpl expected{ ++ {"test_header", "test_value"}, {"etag", "original_etag"}, {"content-length", "123456"}, ++ {"content-range", "654321"}, {"vary", "original_vary"}, ++ }; ++ EXPECT_THAT(&headers, HeaderMapEqualIgnoreOrder(&expected)); ++} ++ ++TEST(CacheEntryUtils, ApplyHeaderUpdateCorrectlyMixesOverwriteIgnoreAddAndPersist) { ++ Http::TestResponseHeaderMapImpl headers{ ++ {"persisted_header", "1"}, ++ {"persisted_header", "2"}, ++ {"overwritten_header", "old"}, ++ }; ++ Http::TestResponseHeaderMapImpl new_headers{ ++ {"overwritten_header", "new"}, ++ {"added_header", "also_new"}, ++ {"etag", "ignored"}, ++ }; ++ applyHeaderUpdate(new_headers, headers); ++ Http::TestResponseHeaderMapImpl expected{ ++ {"persisted_header", "1"}, ++ {"persisted_header", "2"}, ++ {"overwritten_header", "new"}, ++ {"added_header", "also_new"}, ++ }; ++ EXPECT_THAT(&headers, HeaderMapEqualIgnoreOrder(&expected)); ++} ++ + } // namespace + } // namespace Cache + } // namespace HttpFilters +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 7dcf99d512..5e9051c7bb 100644 +--- a/test/extensions/filters/http/health_check/health_check_test.cc ++++ b/test/extensions/filters/http/health_check/health_check_test.cc +@@ -71,9 +71,9 @@ public: + public: + MockHealthCheckCluster(uint64_t membership_total, uint64_t membership_healthy, + uint64_t membership_degraded = 0) { +- info()->stats().membership_total_.set(membership_total); +- info()->stats().membership_healthy_.set(membership_healthy); +- info()->stats().membership_degraded_.set(membership_degraded); ++ info()->endpointStats().membership_total_.set(membership_total); ++ info()->endpointStats().membership_healthy_.set(membership_healthy); ++ info()->endpointStats().membership_degraded_.set(membership_degraded); + } + }; + }; +diff --git a/test/extensions/matching/actions/format_string/BUILD b/test/extensions/matching/actions/format_string/BUILD +new file mode 100644 +index 0000000000..a9a49de8c8 +--- /dev/null ++++ b/test/extensions/matching/actions/format_string/BUILD +@@ -0,0 +1,24 @@ ++load( ++ "//bazel:envoy_build_system.bzl", ++ "envoy_package", ++) ++load( ++ "//test/extensions:extensions_build_system.bzl", ++ "envoy_extension_cc_test", ++) ++ ++licenses(["notice"]) # Apache 2 ++ ++envoy_package() ++ ++envoy_extension_cc_test( ++ name = "config_test", ++ srcs = ["config_test.cc"], ++ extension_names = ["envoy.matching.actions.format_string"], ++ deps = [ ++ "//source/extensions/matching/actions/format_string:config", ++ "//test/mocks/network:network_mocks", ++ "//test/mocks/server:instance_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 +new file mode 100644 +index 0000000000..c52cc8f230 +--- /dev/null ++++ b/test/extensions/matching/actions/format_string/config_test.cc +@@ -0,0 +1,60 @@ ++#include "source/extensions/matching/actions/format_string/config.h" ++ ++#include "test/mocks/network/mocks.h" ++#include "test/mocks/server/instance.h" ++#include "test/mocks/stream_info/mocks.h" ++ ++#include "gtest/gtest.h" ++ ++namespace Envoy { ++namespace Extensions { ++namespace Matching { ++namespace Actions { ++namespace FormatString { ++ ++TEST(ConfigTest, TestConfig) { ++ const std::string yaml_string = R"EOF( ++ text_format_source: ++ inline_string: "%DYNAMIC_METADATA(com.test_filter:test_key)%" ++)EOF"; ++ ++ envoy::config::core::v3::SubstitutionFormatString config; ++ TestUtility::loadFromYaml(yaml_string, config); ++ ++ testing::NiceMock factory_context; ++ ActionFactory factory; ++ auto action_cb = factory.createActionFactoryCb(config, factory_context, ++ ProtobufMessage::getStrictValidationVisitor()); ++ ASSERT_NE(nullptr, action_cb); ++ auto action = action_cb(); ++ ASSERT_NE(nullptr, action); ++ const auto& typed_action = action->getTyped(); ++ ++ Server::FilterChainsByName chains; ++ auto chain = std::make_shared>(); ++ chains.emplace("foo", chain); ++ ++ testing::NiceMock info; ++ { ++ auto result = typed_action.get(chains, info); ++ EXPECT_EQ(nullptr, result); ++ } ++ ++ { ++ const std::string metadata_string = R"EOF( ++ filter_metadata: ++ com.test_filter: ++ test_key: foo ++)EOF"; ++ TestUtility::loadFromYaml(metadata_string, info.metadata_); ++ auto result = typed_action.get(chains, info); ++ ASSERT_NE(nullptr, result); ++ ASSERT_EQ(chain.get(), result); ++ } ++} ++ ++} // namespace FormatString ++} // namespace Actions ++} // namespace Matching ++} // namespace Extensions ++} // namespace Envoy +diff --git a/test/extensions/stats_sinks/common/statsd/statsd_test.cc b/test/extensions/stats_sinks/common/statsd/statsd_test.cc +index 3d20059442..8e036f008e 100644 +--- a/test/extensions/stats_sinks/common/statsd/statsd_test.cc ++++ b/test/extensions/stats_sinks/common/statsd/statsd_test.cc +@@ -225,13 +225,13 @@ TEST_F(TcpStatsdSinkTest, Overflow) { +  + // Synthetically set buffer above high watermark. Make sure we don't write anything. + cluster_manager_.active_clusters_["fake_cluster"] +- ->info_->stats() ++ ->info_->trafficStats() + .upstream_cx_tx_bytes_buffered_.set(1024 * 1024 * 17); + sink_->flush(snapshot_); +  + // Lower and make sure we write. + cluster_manager_.active_clusters_["fake_cluster"] +- ->info_->stats() ++ ->info_->trafficStats() + .upstream_cx_tx_bytes_buffered_.set(1024 * 1024 * 15); + expectCreateConnection(); + EXPECT_CALL(*connection_, write(BufferStringEqual("envoy.test_counter:1|c\n"), _)); +@@ -239,7 +239,7 @@ TEST_F(TcpStatsdSinkTest, Overflow) { +  + // Raise and make sure we don't write and kill connection. + cluster_manager_.active_clusters_["fake_cluster"] +- ->info_->stats() ++ ->info_->trafficStats() + .upstream_cx_tx_bytes_buffered_.set(1024 * 1024 * 17); + EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)); + sink_->flush(snapshot_); +diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc +index 866cbc7df0..53dd5278ab 100644 +--- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc ++++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc +@@ -82,9 +82,9 @@ public: + ON_CALL(error_4xx_counter_, value()).WillByDefault(Return((i + 1) * error_4xx_step)); + ON_CALL(retry_4xx_counter_, value()).WillByDefault(Return((i + 1) * error_4xx_retry_step)); + ON_CALL(success_counter_, value()).WillByDefault(Return((i + 1) * success_step)); +- cluster_info_->stats().upstream_rq_timeout_.add(timeout_step); +- cluster_info_->stats().upstream_rq_per_try_timeout_.add(timeout_retry_step); +- cluster_info_->stats().upstream_rq_pending_overflow_.add(rejected_step); ++ cluster_info_->trafficStats().upstream_rq_timeout_.add(timeout_step); ++ cluster_info_->trafficStats().upstream_rq_per_try_timeout_.add(timeout_retry_step); ++ cluster_info_->trafficStats().upstream_rq_pending_overflow_.add(rejected_step); + } +  + NiceMock cluster_; +diff --git a/test/integration/eds_integration_test.cc b/test/integration/eds_integration_test.cc +index dbac39a9c7..d3be9d2ccb 100644 +--- a/test/integration/eds_integration_test.cc ++++ b/test/integration/eds_integration_test.cc +@@ -56,32 +56,45 @@ public: + } + } +  ++ struct EndpointSettingOptions { ++ uint32_t total_endpoints = 1; ++ uint32_t healthy_endpoints = 0; ++ uint32_t degraded_endpoints = 0; ++ uint32_t disable_active_hc_endpoints = 0; ++ absl::optional overprovisioning_factor = absl::nullopt; ++ }; ++ + // We need to supply the endpoints via EDS to provide health status. Use a + // filesystem delivery to simplify test mechanics. +- void setEndpoints(uint32_t total_endpoints, uint32_t healthy_endpoints, +- uint32_t degraded_endpoints, bool remaining_unhealthy = true, +- absl::optional overprovisioning_factor = absl::nullopt, ++ void setEndpoints(const EndpointSettingOptions& endpoint_setting, bool remaining_unhealthy = true, + bool await_update = true) { +- ASSERT(total_endpoints >= healthy_endpoints + degraded_endpoints); ++ ASSERT(endpoint_setting.total_endpoints >= ++ endpoint_setting.healthy_endpoints + endpoint_setting.degraded_endpoints); ++ ASSERT(endpoint_setting.total_endpoints >= endpoint_setting.disable_active_hc_endpoints); + envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; + cluster_load_assignment.set_cluster_name("cluster_0"); +- if (overprovisioning_factor.has_value()) { ++ if (endpoint_setting.overprovisioning_factor.has_value()) { + cluster_load_assignment.mutable_policy()->mutable_overprovisioning_factor()->set_value( +- overprovisioning_factor.value()); ++ endpoint_setting.overprovisioning_factor.value()); + } + auto* locality_lb_endpoints = cluster_load_assignment.add_endpoints(); +  +- for (uint32_t i = 0; i < total_endpoints; ++i) { ++ for (uint32_t i = 0; i < endpoint_setting.total_endpoints; ++i) { + auto* endpoint = locality_lb_endpoints->add_lb_endpoints(); + setUpstreamAddress(i, *endpoint); + // First N endpoints are degraded, next M are healthy and the remaining endpoints are + // unhealthy or unknown depending on remaining_unhealthy. +- if (i < degraded_endpoints) { ++ if (i < endpoint_setting.degraded_endpoints) { + endpoint->set_health_status(envoy::config::core::v3::DEGRADED); +- } else if (i >= healthy_endpoints + degraded_endpoints) { ++ } else if (i >= endpoint_setting.healthy_endpoints + endpoint_setting.degraded_endpoints) { + endpoint->set_health_status(remaining_unhealthy ? envoy::config::core::v3::UNHEALTHY + : envoy::config::core::v3::UNKNOWN); + } ++ if (i < endpoint_setting.disable_active_hc_endpoints) { ++ endpoint->mutable_endpoint() ++ ->mutable_health_check_config() ++ ->set_disable_active_health_check(true); ++ } + } +  + if (await_update) { +@@ -136,7 +149,7 @@ public: + health_check->mutable_http_health_check()->set_path("/healthcheck"); + health_check->mutable_http_health_check()->set_codec_client_type(codec_client_type_); + } +- setEndpoints(0, 0, 0, true, absl::nullopt, false); ++ setEndpoints({/*total_endpoints=*/0}, true, false); +  + if (cluster_modifier != nullptr) { + cluster_modifier(cluster_); +@@ -175,7 +188,9 @@ TEST_P(EdsIntegrationTest, Http2UpdatePriorities) { + TEST_P(EdsIntegrationTest, Http2HcClusterRewarming) { + codec_client_type_ = envoy::type::v3::HTTP2; + initializeTest(true); +- setEndpoints(1, 0, 0, false); ++ ++ // There is 1 total endpoint. ++ setEndpoints(EndpointSettingOptions(), false); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); +  +@@ -219,11 +234,51 @@ TEST_P(EdsIntegrationTest, Http2HcClusterRewarming) { + fake_upstream_connection.reset(); + } +  ++TEST_P(EdsIntegrationTest, EndpointDisableActiveHCFlag) { ++ initializeTest(true); ++ EndpointSettingOptions options; ++ ++ // Total 1 endpoints with all active health check enabled. ++ setEndpoints(options, false); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); ++ EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); ++ ++ // 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); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); ++ ++ // Disable Active Healthy Check for the host. EDS Unknown is considered as healthy ++ options.disable_active_hc_endpoints = 1; ++ setEndpoints(options, false); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); ++ ++ // Set the host as unhealthy through EDS. ++ setEndpoints(options, true); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); ++ EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); ++ ++ // Set host as healthy through EDS. ++ options.healthy_endpoints = 1; ++ setEndpoints(options, false); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); ++ EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); ++ ++ // Clear out the host and verify the host is gone. ++ setEndpoints({/*total_endpoints=*/0}); ++ EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_total")->value()); ++} ++ + // Verify that a host stabilized via active health checking which is first removed from EDS and + // then fails health checking is removed. + TEST_P(EdsIntegrationTest, RemoveAfterHcFail) { + initializeTest(true); +- setEndpoints(1, 0, 0, false); ++ EndpointSettingOptions options; ++ setEndpoints(options, false); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); +  +@@ -234,7 +289,8 @@ TEST_P(EdsIntegrationTest, RemoveAfterHcFail) { + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); +  + // Clear out the host and verify the host is still healthy. +- setEndpoints(0, 0, 0); ++ options.total_endpoints = 0; ++ setEndpoints(options); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); +  +@@ -253,7 +309,8 @@ TEST_P(EdsIntegrationTest, FinishWarmingIgnoreHealthCheck) { + initializeTest(true, [](envoy::config::cluster::v3::Cluster& cluster) { + cluster.set_ignore_health_on_host_removal(true); + }); +- setEndpoints(1, 0, 0, false); ++ EndpointSettingOptions options; ++ setEndpoints(options, false); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster_manager.warming_clusters")->value()); +@@ -266,7 +323,8 @@ TEST_P(EdsIntegrationTest, FinishWarmingIgnoreHealthCheck) { +  + // 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. +- setEndpoints(0, 0, 0, true, absl::nullopt, false); ++ options.total_endpoints = 0; ++ setEndpoints(options, true, false); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + } +  +@@ -276,7 +334,7 @@ TEST_P(EdsIntegrationTest, EndpointWarmingSuccessfulHc) { +  + // Endpoints are initially excluded. + initializeTest(true); +- setEndpoints(1, 0, 0, false); ++ setEndpoints(EndpointSettingOptions(), false); +  + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_excluded")->value()); +@@ -298,7 +356,7 @@ TEST_P(EdsIntegrationTest, EndpointWarmingFailedHc) { +  + // Endpoints are initially excluded. + initializeTest(true); +- setEndpoints(1, 0, 0, false); ++ setEndpoints(EndpointSettingOptions(), false); +  + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_excluded")->value()); +@@ -316,32 +374,40 @@ TEST_P(EdsIntegrationTest, EndpointWarmingFailedHc) { + // Validate that health status updates are consumed from EDS. + TEST_P(EdsIntegrationTest, HealthUpdate) { + initializeTest(false); ++ EndpointSettingOptions options; + // Initial state, no cluster members. + EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.membership_change")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + // 2/2 healthy endpoints. +- setEndpoints(2, 2, 0); ++ options.total_endpoints = 2; ++ options.healthy_endpoints = 2; ++ setEndpoints(options); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.membership_change")->value()); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + // Drop to 0/2 healthy endpoints. +- setEndpoints(2, 0, 0); ++ options.healthy_endpoints = 0; ++ setEndpoints(options); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.membership_change")->value()); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(0, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + // Increase to 1/2 healthy endpoints. +- setEndpoints(2, 1, 0); ++ options.healthy_endpoints = 1; ++ setEndpoints(options); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.membership_change")->value()); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(1, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + // Add host and modify health to 2/3 healthy endpoints. +- setEndpoints(3, 2, 0); ++ options.total_endpoints = 3; ++ options.healthy_endpoints = 2; ++ setEndpoints(options); + EXPECT_EQ(2, test_server_->counter("cluster.cluster_0.membership_change")->value()); + EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + // Modify health to 2/3 healthy and 1/3 degraded. +- setEndpoints(3, 2, 1); ++ options.degraded_endpoints = 1; ++ setEndpoints(options); + EXPECT_EQ(2, test_server_->counter("cluster.cluster_0.membership_change")->value()); + EXPECT_EQ(3, test_server_->gauge("cluster.cluster_0.membership_total")->value()); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); +@@ -352,7 +418,10 @@ TEST_P(EdsIntegrationTest, HealthUpdate) { + TEST_P(EdsIntegrationTest, OverprovisioningFactorUpdate) { + initializeTest(false); + // Default overprovisioning factor. +- setEndpoints(4, 4, 0); ++ EndpointSettingOptions options; ++ options.total_endpoints = 4; ++ options.healthy_endpoints = 4; ++ setEndpoints(options); + auto get_and_compare = [this](const uint32_t expected_factor) { + const auto& cluster_map = test_server_->server().clusterManager().clusters(); + EXPECT_EQ(1, cluster_map.active_clusters_.size()); +@@ -366,7 +435,8 @@ TEST_P(EdsIntegrationTest, OverprovisioningFactorUpdate) { + get_and_compare(Envoy::Upstream::kDefaultOverProvisioningFactor); +  + // Use new overprovisioning factor 200. +- setEndpoints(4, 4, 0, true, 200); ++ options.overprovisioning_factor = 200; ++ setEndpoints(options); + get_and_compare(200); + } +  +@@ -426,7 +496,7 @@ TEST_P(EdsIntegrationTest, StatsReadyFilter) { + cleanupUpstreamAndDownstream(); +  + // 2/2 healthy endpoints. +- setEndpoints(2, 2, 0); ++ setEndpoints({/*total_endpoint*/ 2, /*healthy_endpoints*/ 2}); + EXPECT_EQ(2, test_server_->gauge("cluster.cluster_0.membership_healthy")->value()); + response = IntegrationUtil::makeSingleRequest(lookupPort("http"), "GET", "/cluster1", "", + downstream_protocol_, version_, "foo.com"); +diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc +index 7201244e34..066820e6e5 100644 +--- a/test/integration/protocol_integration_test.cc ++++ b/test/integration/protocol_integration_test.cc +@@ -3915,4 +3915,44 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidResponseHeaderName) { + } + #endif +  ++TEST_P(DownstreamProtocolIntegrationTest, InvalidSchemeHeaderWithWhitespace) { ++ useAccessLog("%RESPONSE_CODE_DETAILS%"); ++ initialize(); ++ codec_client_ = makeHttpConnection(lookupPort("http")); ++ ++ // Start the request. ++ auto response = codec_client_->makeHeaderOnlyRequest( ++ Http::TestRequestHeaderMapImpl{{":method", "GET"}, ++ {":path", "/test/long/url"}, ++ {":scheme", "/admin http"}, ++ {":authority", "sni.lyft.com"}}); ++ ++#ifdef ENVOY_ENABLE_UHV ++ if (downstreamProtocol() != Http::CodecType::HTTP1) { ++ ASSERT_TRUE(response->waitForReset()); ++ EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); ++ return; ++ } ++#else ++ if (downstreamProtocol() == Http::CodecType::HTTP2 && ++ GetParam().http2_implementation == Http2Impl::Nghttp2) { ++ ASSERT_TRUE(response->waitForReset()); ++ EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); ++ return; ++ } ++#endif ++ // Other HTTP codecs accept the bad scheme but the Envoy should replace it with a valid one. ++ waitForNextUpstreamRequest(); ++ if (upstreamProtocol() == Http::CodecType::HTTP1) { ++ // The scheme header is not conveyed in HTTP/1. ++ EXPECT_EQ(nullptr, upstream_request_->headers().Scheme()); ++ } else { ++ EXPECT_THAT(upstream_request_->headers(), HeaderValueOf(Http::Headers::get().Scheme, "http")); ++ } ++ upstream_request_->encodeHeaders(default_response_headers_, true); ++ ASSERT_TRUE(response->waitForEndStream()); ++ ASSERT_TRUE(response->complete()); ++ EXPECT_EQ("200", response->headers().getStatusValue()); ++} ++ + } // namespace Envoy +diff --git a/test/mocks/upstream/cluster_info.cc b/test/mocks/upstream/cluster_info.cc +index ba3a767c93..a210516d5e 100644 +--- a/test/mocks/upstream/cluster_info.cc ++++ b/test/mocks/upstream/cluster_info.cc +@@ -54,11 +54,15 @@ MockClusterInfo::MockClusterInfo() + : http2_options_(::Envoy::Http2::Utility::initializeAndValidateOptions( + envoy::config::core::v3::Http2ProtocolOptions())), + stat_names_(stats_store_.symbolTable()), ++ config_update_stats_names_(stats_store_.symbolTable()), ++ lb_stat_names_(stats_store_.symbolTable()), endpoint_stat_names_(stats_store_.symbolTable()), + cluster_load_report_stat_names_(stats_store_.symbolTable()), + cluster_circuit_breakers_stat_names_(stats_store_.symbolTable()), + cluster_request_response_size_stat_names_(stats_store_.symbolTable()), + cluster_timeout_budget_stat_names_(stats_store_.symbolTable()), + stats_(ClusterInfoImpl::generateStats(stats_store_, stat_names_)), ++ config_update_stats_(config_update_stats_names_, stats_store_), ++ lb_stats_(lb_stat_names_, stats_store_), endpoint_stats_(endpoint_stat_names_, stats_store_), + transport_socket_matcher_(new NiceMock()), + load_report_stats_(ClusterInfoImpl::generateLoadReportStats(load_report_stats_store_, + cluster_load_report_stat_names_)), +@@ -94,7 +98,10 @@ MockClusterInfo::MockClusterInfo() + .WillByDefault(ReturnPointee(&max_response_headers_count_)); + ON_CALL(*this, maxRequestsPerConnection()) + .WillByDefault(ReturnPointee(&max_requests_per_connection_)); +- ON_CALL(*this, stats()).WillByDefault(ReturnRef(stats_)); ++ ON_CALL(*this, trafficStats()).WillByDefault(ReturnRef(stats_)); ++ ON_CALL(*this, lbStats()).WillByDefault(ReturnRef(lb_stats_)); ++ ON_CALL(*this, configUpdateStats()).WillByDefault(ReturnRef(config_update_stats_)); ++ ON_CALL(*this, endpointStats()).WillByDefault(ReturnRef(endpoint_stats_)); + ON_CALL(*this, statsScope()).WillByDefault(ReturnRef(stats_store_)); + // TODO(incfly): The following is a hack because it's not possible to directly embed + // a mock transport socket factory matcher due to circular dependencies. Fix this up in a follow +diff --git a/test/mocks/upstream/cluster_info.h b/test/mocks/upstream/cluster_info.h +index bb6fded4f2..a0a31e0d6c 100644 +--- a/test/mocks/upstream/cluster_info.h ++++ b/test/mocks/upstream/cluster_info.h +@@ -151,7 +151,10 @@ public: + MOCK_METHOD(const std::string&, observabilityName, (), (const)); + MOCK_METHOD(ResourceManager&, resourceManager, (ResourcePriority priority), (const)); + MOCK_METHOD(TransportSocketMatcher&, transportSocketMatcher, (), (const)); +- MOCK_METHOD(ClusterStats&, stats, (), (const)); ++ MOCK_METHOD(ClusterTrafficStats&, trafficStats, (), (const)); ++ MOCK_METHOD(ClusterLbStats&, lbStats, (), (const)); ++ MOCK_METHOD(ClusterEndpointStats&, endpointStats, (), (const)); ++ MOCK_METHOD(ClusterConfigUpdateStats&, configUpdateStats, (), (const)); + MOCK_METHOD(Stats::Scope&, statsScope, (), (const)); + MOCK_METHOD(ClusterLoadReportStats&, loadReportStats, (), (const)); + MOCK_METHOD(ClusterRequestResponseSizeStatsOptRef, requestResponseSizeStats, (), (const)); +@@ -196,12 +199,18 @@ public: + uint64_t max_requests_per_connection_{}; + uint32_t max_response_headers_count_{Http::DEFAULT_MAX_HEADERS_COUNT}; + NiceMock stats_store_; +- ClusterStatNames stat_names_; ++ ClusterTrafficStatNames stat_names_; ++ ClusterConfigUpdateStatNames config_update_stats_names_; ++ ClusterLbStatNames lb_stat_names_; ++ ClusterEndpointStatNames endpoint_stat_names_; + ClusterLoadReportStatNames cluster_load_report_stat_names_; + ClusterCircuitBreakersStatNames cluster_circuit_breakers_stat_names_; + ClusterRequestResponseSizeStatNames cluster_request_response_size_stat_names_; + ClusterTimeoutBudgetStatNames cluster_timeout_budget_stat_names_; +- ClusterStats stats_; ++ ClusterTrafficStats stats_; ++ ClusterConfigUpdateStats config_update_stats_; ++ ClusterLbStats lb_stats_; ++ ClusterEndpointStats endpoint_stats_; + Upstream::TransportSocketMatcherPtr transport_socket_matcher_; + NiceMock load_report_stats_store_; + ClusterLoadReportStats load_report_stats_; +diff --git a/test/mocks/upstream/cluster_manager.cc b/test/mocks/upstream/cluster_manager.cc +index 05c5afcd8a..3b588bf67d 100644 +--- a/test/mocks/upstream/cluster_manager.cc ++++ b/test/mocks/upstream/cluster_manager.cc +@@ -15,7 +15,9 @@ using ::testing::ReturnRef; + MockClusterManager::MockClusterManager(TimeSource&) : MockClusterManager() {} +  + MockClusterManager::MockClusterManager() +- : cluster_stat_names_(*symbol_table_), cluster_load_report_stat_names_(*symbol_table_), ++ : cluster_stat_names_(*symbol_table_), cluster_config_update_stat_names_(*symbol_table_), ++ cluster_lb_stat_names_(*symbol_table_), cluster_endpoint_stat_names_(*symbol_table_), ++ cluster_load_report_stat_names_(*symbol_table_), + cluster_circuit_breakers_stat_names_(*symbol_table_), + cluster_request_response_size_stat_names_(*symbol_table_), + cluster_timeout_budget_stat_names_(*symbol_table_) { +diff --git a/test/mocks/upstream/cluster_manager.h b/test/mocks/upstream/cluster_manager.h +index 59dfce2e01..6a068671ae 100644 +--- a/test/mocks/upstream/cluster_manager.h ++++ b/test/mocks/upstream/cluster_manager.h +@@ -56,7 +56,14 @@ public: + MOCK_METHOD(ClusterUpdateCallbacksHandle*, addThreadLocalClusterUpdateCallbacks_, + (ClusterUpdateCallbacks & callbacks)); + MOCK_METHOD(Config::SubscriptionFactory&, subscriptionFactory, ()); +- const ClusterStatNames& clusterStatNames() const override { return cluster_stat_names_; } ++ const ClusterTrafficStatNames& clusterStatNames() const override { return cluster_stat_names_; } ++ const ClusterConfigUpdateStatNames& clusterConfigUpdateStatNames() const override { ++ return cluster_config_update_stat_names_; ++ } ++ const ClusterEndpointStatNames& clusterEndpointStatNames() const override { ++ return cluster_endpoint_stat_names_; ++ } ++ const ClusterLbStatNames& clusterLbStatNames() const override { return cluster_lb_stat_names_; } + const ClusterLoadReportStatNames& clusterLoadReportStatNames() const override { + return cluster_load_report_stat_names_; + } +@@ -88,7 +95,10 @@ public: + absl::flat_hash_map> active_clusters_; + absl::flat_hash_map> warming_clusters_; + Stats::TestUtil::TestSymbolTable symbol_table_; +- ClusterStatNames cluster_stat_names_; ++ ClusterTrafficStatNames cluster_stat_names_; ++ ClusterConfigUpdateStatNames cluster_config_update_stat_names_; ++ ClusterLbStatNames cluster_lb_stat_names_; ++ ClusterEndpointStatNames cluster_endpoint_stat_names_; + ClusterLoadReportStatNames cluster_load_report_stat_names_; + ClusterCircuitBreakersStatNames cluster_circuit_breakers_stat_names_; + ClusterRequestResponseSizeStatNames cluster_request_response_size_stat_names_; +diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h +index 6e8e9e4168..eebd9099de 100644 +--- a/test/mocks/upstream/host.h ++++ b/test/mocks/upstream/host.h +@@ -163,6 +163,11 @@ public: + return locality_zone_stat_name_->statName(); + } +  ++ 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; ++ } ++ + MOCK_METHOD(Network::Address::InstanceConstSharedPtr, address, (), (const)); + MOCK_METHOD(const std::vector&, addressList, (), + (const)); +@@ -213,6 +218,7 @@ public: + LoadMetricStatsImpl load_metric_stats_; + mutable Stats::TestUtil::TestSymbolTable symbol_table_; + mutable std::unique_ptr locality_zone_stat_name_; ++ bool disable_active_health_check_ = false; + }; +  + } // namespace Upstream +diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml +index d85c9ac503..3d787139db 100644 +--- a/tools/extensions/extensions_schema.yaml ++++ b/tools/extensions/extensions_schema.yaml +@@ -106,6 +106,7 @@ categories: + - envoy.rbac.matchers + - envoy.access_loggers.extension_filters + - envoy.http.stateful_session ++- envoy.matching.action + - envoy.matching.http.input + - envoy.matching.http.custom_matchers + - envoy.matching.network.input diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 41a3b7a543d1c..b805eb2cf69c8 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -25,7 +25,6 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" -#include "absl/strings/str_format.h" #include "gtest/gtest.h" namespace Envoy { diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index 9cdfda6a4d20e..101bc8e071c75 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -5,6 +5,7 @@ #include #include +#include "absl/strings/str_format.h" #include "envoy/config/endpoint/v3/endpoint_components.pb.h" #include "envoy/server/process_context.h" #include "envoy/service/discovery/v3/discovery.pb.h" From f15e6e77d897b98e90a2347b72a017b3aba59c93 Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Tue, 15 Nov 2022 16:46:44 -0500 Subject: [PATCH 7/7] format :-( Signed-off-by: Alyssa Wilk --- test/integration/base_integration_test.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index 101bc8e071c75..63824e58963ff 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -5,7 +5,6 @@ #include #include -#include "absl/strings/str_format.h" #include "envoy/config/endpoint/v3/endpoint_components.pb.h" #include "envoy/server/process_context.h" #include "envoy/service/discovery/v3/discovery.pb.h" @@ -24,6 +23,7 @@ #include "test/test_common/environment.h" #include "test/test_common/test_time.h" +#include "absl/strings/str_format.h" #include "absl/types/optional.h" #if defined(ENVOY_CONFIG_COVERAGE)