diff --git a/api/envoy/api/v2/core/health_check.proto b/api/envoy/api/v2/core/health_check.proto index 94c3f7817962f..ad35ca61536a0 100644 --- a/api/envoy/api/v2/core/health_check.proto +++ b/api/envoy/api/v2/core/health_check.proto @@ -95,7 +95,9 @@ message HealthCheck { string service_name = 5; // Specifies a list of HTTP headers that should be added to each request that is sent to the - // health checked cluster. + // health checked cluster. For more information, including details on header value syntax, see + // the documentation on :ref:`custom request headers + // `. repeated core.HeaderValueOption request_headers_to_add = 6; // If set, health checks will be made using http/2. diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index a4090a47eea4b..9e78edf9cf284 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -14,6 +14,8 @@ Version history * health check: added support for :ref:`custom health check `. * health check: added support for :ref:`specifying jitter as a percentage `. * health_check: added support for :ref:`health check event logging `. +* health_check: added support for specifying :ref:`custom request headers ` + to HTTP health checker requests. * http: added support for a per-stream idle timeout. This applies at both :ref:`connection manager ` and :ref:`per-route granularity `. The timeout diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index faeeaf1a0672c..0e135b26eae43 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -10,6 +10,7 @@ #include "common/config/well_known_names.h" #include "common/grpc/common.h" #include "common/http/header_map_impl.h" +#include "common/network/address_impl.h" #include "common/router/router.h" #include "common/upstream/host_utility.h" @@ -98,7 +99,13 @@ HttpHealthCheckerImpl::HttpHealthCheckerImpl(const Cluster& cluster, HttpHealthCheckerImpl::HttpActiveHealthCheckSession::HttpActiveHealthCheckSession( HttpHealthCheckerImpl& parent, const HostSharedPtr& host) - : ActiveHealthCheckSession(parent, host), parent_(parent) {} + : ActiveHealthCheckSession(parent, host), parent_(parent), + hostname_(parent_.host_value_.empty() ? parent_.cluster_.info()->name() + : parent_.host_value_), + protocol_(parent_.codec_client_type_ == Http::CodecClient::Type::HTTP1 + ? Http::Protocol::Http11 + : Http::Protocol::Http2), + local_address_(std::make_shared("127.0.0.1")) {} HttpHealthCheckerImpl::HttpActiveHealthCheckSession::~HttpActiveHealthCheckSession() { if (client_) { @@ -127,9 +134,6 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onEvent(Network::Conne } } -const RequestInfo::RequestInfoImpl - HttpHealthCheckerImpl::HttpActiveHealthCheckSession::REQUEST_INFO; - void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { if (!client_) { Upstream::Host::CreateConnectionData conn = @@ -144,13 +148,15 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { Http::HeaderMapImpl request_headers{ {Http::Headers::get().Method, "GET"}, - {Http::Headers::get().Host, - parent_.host_value_.empty() ? parent_.cluster_.info()->name() : parent_.host_value_}, + {Http::Headers::get().Host, hostname_}, {Http::Headers::get().Path, parent_.path_}, {Http::Headers::get().UserAgent, Http::Headers::get().UserAgentValues.EnvoyHealthChecker}}; Router::FilterUtility::setUpstreamScheme(request_headers, *parent_.cluster_.info()); - - parent_.request_headers_parser_->evaluateHeaders(request_headers, REQUEST_INFO); + RequestInfo::RequestInfoImpl request_info(protocol_); + request_info.setDownstreamLocalAddress(local_address_); + request_info.setDownstreamRemoteAddress(local_address_); + request_info.onUpstreamHostSelected(host_); + parent_.request_headers_parser_->evaluateHeaders(request_headers, request_info); request_encoder_->encodeHeaders(request_headers, true); request_encoder_ = nullptr; } diff --git a/source/common/upstream/health_checker_impl.h b/source/common/upstream/health_checker_impl.h index 2dc1c66e41b91..0db4bfd70a3fa 100644 --- a/source/common/upstream/health_checker_impl.h +++ b/source/common/upstream/health_checker_impl.h @@ -90,13 +90,14 @@ class HttpHealthCheckerImpl : public HealthCheckerImplBase { HttpActiveHealthCheckSession& parent_; }; - static const RequestInfo::RequestInfoImpl REQUEST_INFO; - ConnectionCallbackImpl connection_callback_impl_{*this}; HttpHealthCheckerImpl& parent_; Http::CodecClientPtr client_; Http::StreamEncoder* request_encoder_{}; Http::HeaderMapPtr response_headers_; + const std::string& hostname_; + const Http::Protocol protocol_; + Network::Address::InstanceConstSharedPtr local_address_; bool expect_reset_{}; }; diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 04d9766fa7fcd..3606f7a9fa2f0 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -322,6 +322,24 @@ class HttpHealthCheckerImplTest : public testing::Test { key: user-agent value: CoolEnvoy/HC append: false + - header: + key: x-protocol + value: "%PROTOCOL%" + - header: + key: x-upstream-metadata + value: "%UPSTREAM_METADATA([\"namespace\", \"key\"])%" + - header: + key: x-downstream-remote-address-without-port + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + - header: + key: x-downstream-local-address + value: "%DOWNSTREAM_LOCAL_ADDRESS%" + - header: + key: x-downstream-local-address-without-port + value: "%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%" + - header: + key: x-start-time + value: "%START_TIME(%s.%9f)%" )EOF"; health_checker_.reset(new TestHttpHealthCheckerImpl(*cluster_, parseHealthCheckFromV2Yaml(yaml), @@ -697,15 +715,28 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { } TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { - const Http::LowerCaseString headerOk("x-envoy-ok"); - const Http::LowerCaseString headerCool("x-envoy-cool"); - const Http::LowerCaseString headerAwesome("x-envoy-awesome"); - - const std::string valueOk = "ok"; - const std::string valueCool = "cool"; - const std::string valueAwesome = "awesome"; - - const std::string valueUserAgent = "CoolEnvoy/HC"; + const Http::LowerCaseString header_ok("x-envoy-ok"); + const Http::LowerCaseString header_cool("x-envoy-cool"); + const Http::LowerCaseString header_awesome("x-envoy-awesome"); + const Http::LowerCaseString upstream_metadata("x-upstream-metadata"); + const Http::LowerCaseString protocol("x-protocol"); + const Http::LowerCaseString downstream_remote_address_without_port( + "x-downstream-remote-address-without-port"); + const Http::LowerCaseString downstream_local_address("x-downstream-local-address"); + const Http::LowerCaseString downstream_local_address_without_port( + "x-downstream-local-address-without-port"); + const Http::LowerCaseString start_time("x-start-time"); + + const std::string value_ok = "ok"; + const std::string value_cool = "cool"; + const std::string value_awesome = "awesome"; + + const std::string value_user_agent = "CoolEnvoy/HC"; + const std::string value_upstream_metadata = "value"; + const std::string value_protocol = "HTTP/1.1"; + const std::string value_downstream_remote_address_without_port = "127.0.0.1"; + const std::string value_downstream_local_address = "127.0.0.1:0"; + const std::string value_downstream_local_address_without_port = "127.0.0.1"; setupServiceValidationWithAdditionalHeaders(); // requires non-empty `service_name` in config. @@ -713,20 +744,39 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { .WillOnce(Return(true)); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)).Times(1); - + auto metadata = TestUtility::parseYaml( + R"EOF( + filter_metadata: + namespace: + key: value + )EOF"); + + std::string current_start_time; cluster_->prioritySet().getMockHostSet(0)->hosts_ = { - makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", metadata)}; 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_EQ(headers.get(headerOk)->value().c_str(), valueOk); - EXPECT_EQ(headers.get(headerCool)->value().c_str(), valueCool); - EXPECT_EQ(headers.get(headerAwesome)->value().c_str(), valueAwesome); - - EXPECT_EQ(headers.UserAgent()->value().c_str(), valueUserAgent); + .WillRepeatedly(Invoke([&](const Http::HeaderMap& headers, bool) { + EXPECT_EQ(headers.get(header_ok)->value().c_str(), value_ok); + EXPECT_EQ(headers.get(header_cool)->value().c_str(), value_cool); + EXPECT_EQ(headers.get(header_awesome)->value().c_str(), value_awesome); + + EXPECT_EQ(headers.UserAgent()->value().c_str(), value_user_agent); + EXPECT_EQ(headers.get(upstream_metadata)->value().c_str(), value_upstream_metadata); + + EXPECT_EQ(headers.get(protocol)->value().c_str(), value_protocol); + EXPECT_EQ(headers.get(downstream_remote_address_without_port)->value().c_str(), + value_downstream_remote_address_without_port); + EXPECT_EQ(headers.get(downstream_local_address)->value().c_str(), + value_downstream_local_address); + EXPECT_EQ(headers.get(downstream_local_address_without_port)->value().c_str(), + value_downstream_local_address_without_port); + + EXPECT_NE(headers.get(start_time)->value().c_str(), current_start_time); + current_start_time = headers.get(start_time)->value().c_str(); })); health_checker_->start(); @@ -738,6 +788,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { absl::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()); + + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + expectStreamCreate(0); + test_sessions_[0]->interval_timer_->callback_(); } TEST_F(HttpHealthCheckerImplTest, ServiceDoesNotMatchFail) { @@ -775,8 +829,6 @@ TEST_F(HttpHealthCheckerImplTest, ServiceNotPresentInResponseFail) { EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)).Times(1); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - ; - ; cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")};