diff --git a/source/common/init/manager_impl.h b/source/common/init/manager_impl.h index 3e1a50f7c059a..de28fccdfd8fb 100644 --- a/source/common/init/manager_impl.h +++ b/source/common/init/manager_impl.h @@ -38,6 +38,7 @@ class ManagerImpl : public Manager, Logger::Loggable { void initialize(const Watcher& watcher) override; void updateWatcher(const Watcher& watcher) override; void dumpUnreadyTargets(envoy::admin::v3::UnreadyTargetsDumps& dumps) override; + uint32_t uninitializedCount() const { return count_; } private: // Callback function with an additional target_name parameter, decrease unready targets count by diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index def04672f4ee7..406d3d286053d 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1995,8 +1995,10 @@ absl::Status ClusterImplBase::parseDropOverloadConfig( void ClusterImplBase::setHealthChecker(const HealthCheckerSharedPtr& health_checker) { ASSERT(!health_checker_); health_checker_ = health_checker; - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.health_check_after_cluster_warming")) { + const bool defer = Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.health_check_after_cluster_warming") && + (!hasZeroInitialFetchTimeout() || init_manager_.uninitializedCount() > 0); + if (!defer) { health_checker_->start(); } health_checker_->addHostCheckCompleteCb( diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index ab9f1dab17d37..14c52f9cedfd2 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -1302,6 +1302,8 @@ class ClusterImplBase : public Cluster, protected Logger::Loggable; diff --git a/test/extensions/clusters/eds/eds_test.cc b/test/extensions/clusters/eds/eds_test.cc index 7a1170121a1bc..0389b68b47c18 100644 --- a/test/extensions/clusters/eds/eds_test.cc +++ b/test/extensions/clusters/eds/eds_test.cc @@ -3612,6 +3612,53 @@ TEST_F(XdstpConfigsEdsTest, DeltaOnConfigUpdateSuccess) { .get() .value()); } + +// EDS-only cluster with initialFetchTimeout: 0s. +// Health checks must start immediately — deferring would block them forever. +TEST_F(EdsTest, HealthCheckStartsImmediatelyWithZeroInitialFetchTimeout) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.health_check_after_cluster_warming", "true"}}); + + resetCluster(R"EOF( + name: name + connect_timeout: 0.25s + type: EDS + lb_policy: ROUND_ROBIN + eds_cluster_config: + service_name: fare + eds_config: + initial_fetch_timeout: 0s + api_config_source: + api_type: GRPC + grpc_services: + envoy_grpc: + cluster_name: eds + )EOF", + Cluster::InitializePhase::Secondary); + + auto health_checker = std::make_shared(); + EXPECT_CALL(*health_checker, start()); + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)).Times(1); + cluster_->setHealthChecker(health_checker); +} + +// EDS cluster with non-zero initialFetchTimeout. +// Health checks must be deferred until warming completes. +TEST_F(EdsTest, HealthCheckDeferredWithNonZeroInitialFetchTimeout) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.health_check_after_cluster_warming", "true"}}); + + // Default cluster config has no explicit initialFetchTimeout, which defaults to 15s. + resetCluster(); + + auto health_checker = std::make_shared(); + EXPECT_CALL(*health_checker, start()).Times(0); + EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)).Times(1); + cluster_->setHealthChecker(health_checker); +} + } // namespace } // namespace Upstream } // namespace Envoy