diff --git a/RAW_RELEASE_NOTES.md b/RAW_RELEASE_NOTES.md index de3ae4e5e49b6..a51e3b6ff48dc 100644 --- a/RAW_RELEASE_NOTES.md +++ b/RAW_RELEASE_NOTES.md @@ -62,3 +62,5 @@ final version. namespace can be used by prepending '@' to a socket path. * Added `GEORADIUS_RO` and `GEORADIUSBYMEMBER_RO` to the Redis command splitter whitelist. * Added support for trusting additional hops in the X-Forwarded-For request header. +* Added setting host header value for http health check request. + diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 1e53bd9b52644..114f0c34d29af 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -281,7 +281,7 @@ HttpHealthCheckerImpl::HttpHealthCheckerImpl(const Cluster& cluster, Runtime::Loader& runtime, Runtime::RandomGenerator& random) : HealthCheckerImplBase(cluster, config, dispatcher, runtime, random), - path_(config.http_health_check().path()) { + path_(config.http_health_check().path()), host_value_(config.http_health_check().host()) { if (!config.http_health_check().service_name().empty()) { service_name_.value(config.http_health_check().service_name()); } @@ -332,7 +332,8 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { Http::HeaderMapImpl request_headers{ {Http::Headers::get().Method, "GET"}, - {Http::Headers::get().Host, parent_.cluster_.info()->name()}, + {Http::Headers::get().Host, + parent_.host_value_.empty() ? parent_.cluster_.info()->name() : parent_.host_value_}, {Http::Headers::get().Path, parent_.path_}, {Http::Headers::get().UserAgent, Http::Headers::get().UserAgentValues.EnvoyHealthChecker}}; diff --git a/source/common/upstream/health_checker_impl.h b/source/common/upstream/health_checker_impl.h index 6d06c37909ab6..021710e0fc506 100644 --- a/source/common/upstream/health_checker_impl.h +++ b/source/common/upstream/health_checker_impl.h @@ -232,6 +232,7 @@ class HttpHealthCheckerImpl : public HealthCheckerImplBase { } const std::string path_; + const std::string host_value_; Optional service_name_; }; diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 8cc72604239d5..fee237521ce17 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -49,6 +49,12 @@ envoy::api::v2::core::HealthCheck parseHealthCheckFromJson(const std::string& js return health_check; } +envoy::api::v2::core::HealthCheck parseHealthCheckFromYaml(const std::string& yaml_string) { + envoy::api::v2::core::HealthCheck health_check; + MessageUtil::loadFromYaml(yaml_string, health_check); + return health_check; +} + envoy::api::v2::core::HealthCheck createGrpcHealthCheckConfig() { envoy::api::v2::core::HealthCheck health_check; health_check.mutable_timeout()->set_seconds(1); @@ -220,6 +226,27 @@ class HttpHealthCheckerImplTest : public testing::Test { }); } + void setupServiceValidationWithCustomHostValueHC(const std::string& host) { + std::string yaml = fmt::format(R"EOF( + timeout: 1s + interval: 1s + interval_jitter: 1s + unhealthy_threshold: 2 + healthy_threshold: 2 + http_health_check: + service_name: locations + path: /healthcheck + host: {0} + )EOF", + host); + + health_checker_.reset(new TestHttpHealthCheckerImpl(*cluster_, parseHealthCheckFromYaml(yaml), + dispatcher_, runtime_, random_)); + health_checker_->addHostCheckCompleteCb([this](HostSharedPtr host, bool changed_state) -> void { + onHostStatus(host, changed_state); + }); + } + void expectSessionCreate() { // Expectations are in LIFO order. TestSessionPtr new_test_session(new TestSession()); @@ -426,6 +453,51 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) { expectSessionCreate(); expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) + .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { + EXPECT_TRUE(headers.Host()); + EXPECT_TRUE(headers.Path()); + EXPECT_NE(nullptr, headers.Host()); + EXPECT_NE(nullptr, headers.Path()); + EXPECT_EQ(headers.Host()->value().c_str(), std::string("fake_cluster")); + EXPECT_EQ(headers.Path()->value().c_str(), std::string("/healthcheck")); + })); + health_checker_->start(); + + EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); + EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) + .WillOnce(Return(45000)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); + Optional health_checked_cluster("locations-production-iad"); + respond(0, "200", false, true, false, health_checked_cluster); + EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthy()); +} + +TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { + std::string host = "www.envoyproxy.io"; + setupServiceValidationWithCustomHostValueHC(host); + // requires non-empty `service_name` in config. + EXPECT_CALL(runtime_.snapshot_, featureEnabled("health_check.verify_cluster", 100)) + .WillOnce(Return(true)); + + EXPECT_CALL(*this, onHostStatus(_, false)).Times(1); + + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + cluster_->info_->stats().upstream_cx_total_.inc(); + expectSessionCreate(); + expectStreamCreate(0); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) + .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { + EXPECT_TRUE(headers.Host()); + EXPECT_TRUE(headers.Path()); + EXPECT_NE(nullptr, headers.Host()); + EXPECT_NE(nullptr, headers.Path()); + EXPECT_EQ(headers.Host()->value().c_str(), std::string(host)); + EXPECT_EQ(headers.Path()->value().c_str(), std::string("/healthcheck")); + })); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _));