diff --git a/include/istio/control/http/report_data.h b/include/istio/control/http/report_data.h index 8cf7326fe0f..dac9998a77e 100644 --- a/include/istio/control/http/report_data.h +++ b/include/istio/control/http/report_data.h @@ -34,6 +34,10 @@ class ReportData { // Get response HTTP headers. virtual std::map GetResponseHeaders() const = 0; + // Get tracing headers from HTTP request headers. + virtual void GetTracingHeaders( + std::map &) const = 0; + // Get additional report info. struct ReportInfo { uint64_t response_total_size; diff --git a/include/istio/utils/attributes_builder.h b/include/istio/utils/attributes_builder.h index 4d1f0996284..43d56b52550 100644 --- a/include/istio/utils/attributes_builder.h +++ b/include/istio/utils/attributes_builder.h @@ -91,6 +91,19 @@ class AttributesBuilder { } } + void InsertStringMap(const std::string &key, + const std::map &string_map) { + if (string_map.size() == 0) { + return; + } + auto entries = (*attributes_->mutable_attributes())[key] + .mutable_string_map_value() + ->mutable_entries(); + for (const auto &map_it : string_map) { + (*entries)[map_it.first] = map_it.second; + } + } + void AddProtoStructStringMap(const std::string &key, const google::protobuf::Struct &struct_map) { if (struct_map.fields().empty()) { diff --git a/src/envoy/http/mixer/filter.cc b/src/envoy/http/mixer/filter.cc index 8c310fa27df..3daccaf1971 100644 --- a/src/envoy/http/mixer/filter.cc +++ b/src/envoy/http/mixer/filter.cc @@ -232,8 +232,8 @@ void Filter::log(const HeaderMap* request_headers, CheckData check_data(*request_headers, stream_info.dynamicMetadata(), decoder_callbacks_->connection()); // response trailer header is not counted to response total size. - ReportData report_data(response_headers, response_trailers, stream_info, - request_total_size_); + ReportData report_data(request_headers, response_headers, response_trailers, + stream_info, request_total_size_); handler_->Report(&check_data, &report_data); } diff --git a/src/envoy/http/mixer/report_data.h b/src/envoy/http/mixer/report_data.h index 2e4e1dd937e..84b18f2468e 100644 --- a/src/envoy/http/mixer/report_data.h +++ b/src/envoy/http/mixer/report_data.h @@ -22,6 +22,7 @@ #include "extensions/filters/http/well_known_names.h" #include "google/protobuf/struct.pb.h" #include "include/istio/control/http/controller.h" +#include "src/envoy/utils/trace_headers.h" #include "src/envoy/utils/utils.h" namespace Envoy { @@ -52,22 +53,26 @@ bool ExtractGrpcStatus(const HeaderMap *headers, class ReportData : public ::istio::control::http::ReportData, public Logger::Loggable { - const HeaderMap *headers_; + const HeaderMap *request_headers_; + const HeaderMap *response_headers_; const HeaderMap *trailers_; const StreamInfo::StreamInfo &info_; uint64_t response_total_size_; uint64_t request_total_size_; public: - ReportData(const HeaderMap *headers, const HeaderMap *response_trailers, + ReportData(const HeaderMap *request_headers, + const HeaderMap *response_headers, + const HeaderMap *response_trailers, const StreamInfo::StreamInfo &info, uint64_t request_total_size) - : headers_(headers), + : request_headers_(request_headers), + response_headers_(response_headers), trailers_(response_trailers), info_(info), response_total_size_(info.bytesSent()), request_total_size_(request_total_size) { - if (headers != nullptr) { - response_total_size_ += headers->byteSize(); + if (response_headers != nullptr) { + response_total_size_ += response_headers->byteSize(); } if (response_trailers != nullptr) { response_total_size_ += response_trailers->byteSize(); @@ -76,8 +81,9 @@ class ReportData : public ::istio::control::http::ReportData, std::map GetResponseHeaders() const override { std::map header_map; - if (headers_) { - Utils::ExtractHeaders(*headers_, ResponseHeaderExclusives, header_map); + if (response_headers_) { + Utils::ExtractHeaders(*response_headers_, ResponseHeaderExclusives, + header_map); } if (trailers_) { Utils::ExtractHeaders(*trailers_, ResponseHeaderExclusives, header_map); @@ -85,6 +91,12 @@ class ReportData : public ::istio::control::http::ReportData, return header_map; } + void GetTracingHeaders( + std::map &tracing_headers) const override { + Utils::FindHeaders(*request_headers_, Utils::TracingHeaderSet, + tracing_headers); + } + void GetReportInfo( ::istio::control::http::ReportData::ReportInfo *data) const override { data->request_body_size = info_.bytesReceived(); @@ -119,7 +131,7 @@ class ReportData : public ::istio::control::http::ReportData, // Check trailer first. // If not response body, grpc-status is in response headers. return ExtractGrpcStatus(trailers_, status) || - ExtractGrpcStatus(headers_, status); + ExtractGrpcStatus(response_headers_, status); } // Get Rbac related attributes. diff --git a/src/envoy/utils/BUILD b/src/envoy/utils/BUILD index 1f90575291c..d80c7558f4c 100644 --- a/src/envoy/utils/BUILD +++ b/src/envoy/utils/BUILD @@ -57,6 +57,7 @@ envoy_cc_library( "header_update.h", "mixer_control.h", "stats.h", + "trace_headers.h", "utils.h", ], repository = "@envoy", diff --git a/src/envoy/utils/trace_headers.h b/src/envoy/utils/trace_headers.h new file mode 100644 index 00000000000..b73b983aaae --- /dev/null +++ b/src/envoy/utils/trace_headers.h @@ -0,0 +1,37 @@ +/* Copyright 2019 Istio Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace Envoy { +namespace Utils { + +// Zipkin B3 headers +const std::string kTraceID = "x-b3-traceid"; +const std::string kSpanID = "x-b3-spanid"; +const std::string kParentSpanID = "x-b3-parentspanid"; +const std::string kSampled = "x-b3-sampled"; + +const std::set TracingHeaderSet = { + kTraceID, + kSpanID, + kParentSpanID, + kSampled, +}; + +} // namespace Utils +} // namespace Envoy diff --git a/src/envoy/utils/utils.cc b/src/envoy/utils/utils.cc index 431c7004c2a..65474bea320 100644 --- a/src/envoy/utils/utils.cc +++ b/src/envoy/utils/utils.cc @@ -61,6 +61,31 @@ void ExtractHeaders(const Http::HeaderMap& header_map, &ctx); } +void FindHeaders(const Http::HeaderMap& header_map, + const std::set& inclusives, + std::map& headers) { + struct Context { + Context(const std::set& inclusives, + std::map& headers) + : inclusives(inclusives), headers(headers) {} + const std::set& inclusives; + std::map& headers; + }; + Context ctx(inclusives, headers); + header_map.iterate( + [](const Http::HeaderEntry& header, + void* context) -> Http::HeaderMap::Iterate { + Context* ctx = static_cast(context); + auto key = std::string(header.key().getStringView()); + auto value = std::string(header.value().getStringView()); + if (ctx->inclusives.count(key) != 0) { + ctx->headers[key] = value; + } + return Http::HeaderMap::Iterate::Continue; + }, + &ctx); +} + bool GetIpPort(const Network::Address::Ip* ip, std::string* str_ip, int* port) { if (ip) { *port = ip->port(); diff --git a/src/envoy/utils/utils.h b/src/envoy/utils/utils.h index 3fa4aac5b21..7102b454a11 100644 --- a/src/envoy/utils/utils.h +++ b/src/envoy/utils/utils.h @@ -31,6 +31,12 @@ void ExtractHeaders(const Http::HeaderMap& header_map, const std::set& exclusives, std::map& headers); +// Find the given headers from the header map and extract them out to the string +// map. +void FindHeaders(const Http::HeaderMap& header_map, + const std::set& inclusives, + std::map& headers); + // Get ip and port from Envoy ip. bool GetIpPort(const Network::Address::Ip* ip, std::string* str_ip, int* port); diff --git a/src/istio/control/http/attributes_builder.cc b/src/istio/control/http/attributes_builder.cc index 32ffa5c8423..8c3fc539a06 100644 --- a/src/istio/control/http/attributes_builder.cc +++ b/src/istio/control/http/attributes_builder.cc @@ -226,6 +226,11 @@ void AttributesBuilder::ExtractReportAttributes( report_data->GetResponseHeaders(); builder.AddStringMap(utils::AttributeName::kResponseHeaders, headers); + std::map tracing_headers; + report_data->GetTracingHeaders(tracing_headers); + builder.InsertStringMap(utils::AttributeName::kRequestHeaders, + tracing_headers); + builder.AddTimestamp(utils::AttributeName::kResponseTime, std::chrono::system_clock::now()); diff --git a/src/istio/control/http/attributes_builder_test.cc b/src/istio/control/http/attributes_builder_test.cc index 09c84bc1567..d447542ce78 100644 --- a/src/istio/control/http/attributes_builder_test.cc +++ b/src/istio/control/http/attributes_builder_test.cc @@ -350,6 +350,21 @@ attributes { int64_value: 8080 } } +attributes { + key: "request.headers" + value { + string_map_value { + entries { + key: "x-b3-traceid" + value: "abc" + } + entries { + key: "x-b3-spanid" + value: "def" + } + } + } +} attributes { key: "response.headers" value { @@ -765,6 +780,11 @@ TEST(AttributesBuilderTest, TestReportAttributes) { map["server"] = "my-server"; return map; })); + EXPECT_CALL(mock_data, GetTracingHeaders(_)) + .WillOnce(Invoke([](std::map &m) { + m["x-b3-traceid"] = "abc"; + m["x-b3-spanid"] = "def"; + })); EXPECT_CALL(mock_data, GetReportInfo(_)) .WillOnce(Invoke([](ReportData::ReportInfo *info) { info->request_body_size = 100; @@ -845,6 +865,11 @@ TEST(AttributesBuilderTest, TestReportAttributesWithDestIP) { map["server"] = "my-server"; return map; })); + EXPECT_CALL(mock_data, GetTracingHeaders(_)) + .WillOnce(Invoke([](std::map &m) { + m["x-b3-traceid"] = "abc"; + m["x-b3-spanid"] = "def"; + })); EXPECT_CALL(mock_data, GetReportInfo(_)) .WillOnce(Invoke([](ReportData::ReportInfo *info) { info->request_body_size = 100; diff --git a/src/istio/control/http/mock_report_data.h b/src/istio/control/http/mock_report_data.h index 64c0402ef9b..2b2b0fddc1d 100644 --- a/src/istio/control/http/mock_report_data.h +++ b/src/istio/control/http/mock_report_data.h @@ -27,6 +27,8 @@ namespace http { class MockReportData : public ReportData { public: MOCK_CONST_METHOD0(GetResponseHeaders, std::map()); + MOCK_CONST_METHOD1(GetTracingHeaders, + void(std::map &)); MOCK_CONST_METHOD1(GetReportInfo, void(ReportInfo *info)); MOCK_CONST_METHOD2(GetDestinationIpPort, bool(std::string *ip, int *port)); MOCK_CONST_METHOD1(GetDestinationUID, bool(std::string *ip)); diff --git a/test/integration/istio_http_integration_test.cc b/test/integration/istio_http_integration_test.cc index 8401b646867..c22293211dc 100644 --- a/test/integration/istio_http_integration_test.cc +++ b/test/integration/istio_http_integration_test.cc @@ -24,6 +24,7 @@ #include "include/istio/utils/attribute_names.h" #include "mixer/v1/mixer.pb.h" #include "src/envoy/utils/filter_names.h" +#include "src/envoy/utils/trace_headers.h" #include "test/integration/http_protocol_integration.h" using ::google::protobuf::util::error::Code; @@ -102,6 +103,7 @@ constexpr char kDestinationUID[] = "kubernetes://dest.pod"; constexpr char kSourceUID[] = "kubernetes://src.pod"; constexpr char kTelemetryBackend[] = "telemetry-backend"; constexpr char kPolicyBackend[] = "policy-backend"; +constexpr char kZipkinBackend[] = "zipkin-backend"; // Generates basic test request header. Http::TestHeaderMapImpl BaseRequestHeaders() { @@ -227,6 +229,10 @@ class IstioHttpIntegrationTest : public HttpProtocolIntegrationTest { fake_upstreams_.emplace_back(new FakeUpstream( 0, FakeHttpConnection::Type::HTTP2, version_, timeSystem())); policy_upstream_ = fake_upstreams_.back().get(); + + fake_upstreams_.emplace_back(new FakeUpstream( + 0, FakeHttpConnection::Type::HTTP2, version_, timeSystem())); + zipkin_upstream_ = fake_upstreams_.back().get(); } void SetUp() override { @@ -239,6 +245,10 @@ class IstioHttpIntegrationTest : public HttpProtocolIntegrationTest { config_helper_.addConfigModifier(addCluster(kTelemetryBackend)); config_helper_.addConfigModifier(addCluster(kPolicyBackend)); + config_helper_.addConfigModifier(addCluster(kZipkinBackend)); + + config_helper_.addConfigModifier(addTracer()); + config_helper_.addConfigModifier(addTracingRate()); HttpProtocolIntegrationTest::initialize(); } @@ -247,6 +257,7 @@ class IstioHttpIntegrationTest : public HttpProtocolIntegrationTest { cleanupConnection(fake_upstream_connection_); cleanupConnection(telemetry_connection_); cleanupConnection(policy_connection_); + cleanupConnection(zipkin_connection_); } ConfigHelper::ConfigModifierFunction addNodeMetadata() { @@ -264,6 +275,33 @@ class IstioHttpIntegrationTest : public HttpProtocolIntegrationTest { }; } + ConfigHelper::ConfigModifierFunction addTracer() { + return [](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* http_tracing = bootstrap.mutable_tracing()->mutable_http(); + http_tracing->set_name("envoy.zipkin"); + auto* tracer_config_fields = + http_tracing->mutable_config()->mutable_fields(); + (*tracer_config_fields)["collector_cluster"].set_string_value( + kZipkinBackend); + (*tracer_config_fields)["collector_endpoint"].set_string_value( + "/api/v1/spans"); + }; + } + + ConfigHelper::HttpModifierFunction addTracingRate() { + return [](envoy::config::filter::network::http_connection_manager::v2:: + HttpConnectionManager& hcm) { + auto* tracing = hcm.mutable_tracing(); + tracing->set_operation_name( + envoy::config::filter::network::http_connection_manager::v2:: + HttpConnectionManager_Tracing_OperationName:: + HttpConnectionManager_Tracing_OperationName_EGRESS); + tracing->mutable_client_sampling()->set_value(100.0); + tracing->mutable_random_sampling()->set_value(100.0); + tracing->mutable_overall_sampling()->set_value(100.0); + }; + } + ConfigHelper::ConfigModifierFunction addCluster(const std::string& name) { return [name](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { auto* cluster = bootstrap.mutable_static_resources()->add_clusters(); @@ -329,6 +367,10 @@ class IstioHttpIntegrationTest : public HttpProtocolIntegrationTest { FakeUpstream* policy_upstream_{}; FakeHttpConnectionPtr policy_connection_{}; FakeStreamPtr policy_request_{}; + + FakeUpstream* zipkin_upstream_{}; + FakeHttpConnectionPtr zipkin_connection_{}; + FakeStreamPtr zipkin_request_{}; }; INSTANTIATE_TEST_CASE_P( @@ -435,5 +477,40 @@ TEST_P(IstioHttpIntegrationTest, GoodJwt) { EXPECT_EQ("200", response->headers().Status()->value().getStringView()); } +TEST_P(IstioHttpIntegrationTest, TracingHeader) { + codec_client_ = + makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = + codec_client_->makeHeaderOnlyRequest(HeadersWithToken(kGoodToken)); + + ::istio::mixer::v1::CheckRequest check_request; + waitForPolicyRequest(&check_request); + sendPolicyResponse(); + + waitForNextUpstreamRequest(0); + // Send backend response. + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, + true); + response->waitForEndStream(); + + ::istio::mixer::v1::ReportRequest report_request; + waitForTelemetryRequest(&report_request); + sendTelemetryResponse(); + + response->waitForEndStream(); + + EXPECT_TRUE(response->complete()); + Http::TestHeaderMapImpl upstream_headers(upstream_request_->headers()); + // Trace headers should be added into upstream request + EXPECT_TRUE(upstream_headers.has(Envoy::Utils::kTraceID)); + EXPECT_TRUE(upstream_headers.has(Envoy::Utils::kSpanID)); + EXPECT_TRUE(upstream_headers.has(Envoy::Utils::kSampled)); + + // span id should be included in default words of report request + EXPECT_THAT( + report_request.default_words(), + ::testing::AllOf(Contains(upstream_headers.get_(Envoy::Utils::kSpanID)))); +} + } // namespace } // namespace Envoy