diff --git a/api/envoy/config/trace/v2/trace.proto b/api/envoy/config/trace/v2/trace.proto index 34f11ec7e3e61..e4e4dec64e95a 100644 --- a/api/envoy/config/trace/v2/trace.proto +++ b/api/envoy/config/trace/v2/trace.proto @@ -150,6 +150,9 @@ message OpenCensusConfig { // "X-Cloud-Trace-Context:" header. CLOUD_TRACE_CONTEXT = 3; + + // X-B3-* headers. + B3 = 4; } // List of incoming trace context headers we will accept. First one found diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 9cb20ae5f0a3c..c0b2205e0ad6c 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -528,6 +528,10 @@ def _io_opencensus_cpp(): name = "opencensus_trace", actual = "@io_opencensus_cpp//opencensus/trace", ) + native.bind( + name = "opencensus_trace_b3", + actual = "@io_opencensus_cpp//opencensus/trace:b3", + ) native.bind( name = "opencensus_trace_cloud_trace_context", actual = "@io_opencensus_cpp//opencensus/trace:cloud_trace_context", diff --git a/source/extensions/tracers/opencensus/BUILD b/source/extensions/tracers/opencensus/BUILD index 87b1854a2d2f9..492648fdf0883 100644 --- a/source/extensions/tracers/opencensus/BUILD +++ b/source/extensions/tracers/opencensus/BUILD @@ -28,6 +28,7 @@ envoy_cc_library( copts = ["-Wno-unused-parameter"], external_deps = [ "opencensus_trace", + "opencensus_trace_b3", "opencensus_trace_cloud_trace_context", "opencensus_trace_grpc_trace_bin", "opencensus_trace_trace_context", diff --git a/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc b/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc index eb9a52d7dbd25..9be9d34848684 100644 --- a/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc +++ b/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc @@ -11,6 +11,7 @@ #include "opencensus/exporters/trace/stackdriver/stackdriver_exporter.h" #include "opencensus/exporters/trace/stdout/stdout_exporter.h" #include "opencensus/exporters/trace/zipkin/zipkin_exporter.h" +#include "opencensus/trace/propagation/b3.h" #include "opencensus/trace/propagation/cloud_trace_context.h" #include "opencensus/trace/propagation/grpc_trace_bin.h" #include "opencensus/trace/propagation/trace_context.h" @@ -32,6 +33,10 @@ class ConstantValues { const Http::LowerCaseString TRACEPARENT{"traceparent"}; const Http::LowerCaseString GRPC_TRACE_BIN{"grpc-trace-bin"}; const Http::LowerCaseString X_CLOUD_TRACE_CONTEXT{"x-cloud-trace-context"}; + const Http::LowerCaseString X_B3_TRACEID{"x-b3-traceid"}; + const Http::LowerCaseString X_B3_SPANID{"x-b3-spanid"}; + const Http::LowerCaseString X_B3_SAMPLED{"x-b3-sampled"}; + const Http::LowerCaseString X_B3_FLAGS{"x-b3-flags"}; }; using Constants = ConstSingleton; @@ -101,6 +106,35 @@ startSpanHelper(const std::string& name, bool traced, const Http::HeaderMap& req } break; } + + case OpenCensusConfig::B3: { + absl::string_view b3_trace_id; + absl::string_view b3_span_id; + absl::string_view b3_sampled; + absl::string_view b3_flags; + const Http::HeaderEntry* h_b3_trace_id = request_headers.get(Constants::get().X_B3_TRACEID); + if (h_b3_trace_id != nullptr) { + b3_trace_id = h_b3_trace_id->value().getStringView(); + } + const Http::HeaderEntry* h_b3_span_id = request_headers.get(Constants::get().X_B3_SPANID); + if (h_b3_span_id != nullptr) { + b3_span_id = h_b3_span_id->value().getStringView(); + } + const Http::HeaderEntry* h_b3_sampled = request_headers.get(Constants::get().X_B3_SAMPLED); + if (h_b3_sampled != nullptr) { + b3_sampled = h_b3_sampled->value().getStringView(); + } + const Http::HeaderEntry* h_b3_flags = request_headers.get(Constants::get().X_B3_FLAGS); + if (h_b3_flags != nullptr) { + b3_flags = h_b3_flags->value().getStringView(); + } + if (h_b3_trace_id != nullptr && h_b3_span_id != nullptr) { + found = true; + parent_ctx = ::opencensus::trace::propagation::FromB3Headers(b3_trace_id, b3_span_id, + b3_sampled, b3_flags); + } + break; + } } // First header found wins. if (found) { @@ -153,16 +187,16 @@ void Span::finishSpan() { span_.End(); } void Span::injectContext(Http::HeaderMap& request_headers) { using OpenCensusConfig = envoy::config::trace::v2::OpenCensusConfig; + const auto& ctx = span_.context(); for (const auto& outgoing : oc_config_.outgoing_trace_context()) { switch (outgoing) { case OpenCensusConfig::TRACE_CONTEXT: - request_headers.setReferenceKey( - Constants::get().TRACEPARENT, - ::opencensus::trace::propagation::ToTraceParentHeader(span_.context())); + request_headers.setReferenceKey(Constants::get().TRACEPARENT, + ::opencensus::trace::propagation::ToTraceParentHeader(ctx)); break; case OpenCensusConfig::GRPC_TRACE_BIN: { - std::string val = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(span_.context()); + std::string val = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(ctx); val = Base64::encode(val.data(), val.size(), /*add_padding=*/false); request_headers.setReferenceKey(Constants::get().GRPC_TRACE_BIN, val); break; @@ -171,7 +205,18 @@ void Span::injectContext(Http::HeaderMap& request_headers) { case OpenCensusConfig::CLOUD_TRACE_CONTEXT: request_headers.setReferenceKey( Constants::get().X_CLOUD_TRACE_CONTEXT, - ::opencensus::trace::propagation::ToCloudTraceContextHeader(span_.context())); + ::opencensus::trace::propagation::ToCloudTraceContextHeader(ctx)); + break; + + case OpenCensusConfig::B3: + request_headers.setReferenceKey(Constants::get().X_B3_TRACEID, + ::opencensus::trace::propagation::ToB3TraceIdHeader(ctx)); + request_headers.setReferenceKey(Constants::get().X_B3_SPANID, + ::opencensus::trace::propagation::ToB3SpanIdHeader(ctx)); + request_headers.setReferenceKey(Constants::get().X_B3_SAMPLED, + ::opencensus::trace::propagation::ToB3SampledHeader(ctx)); + // OpenCensus's trace context propagation doesn't produce the + // "X-B3-Flags:" header. break; } } diff --git a/test/extensions/tracers/opencensus/config_test.cc b/test/extensions/tracers/opencensus/config_test.cc index 1f549cd4afb14..686f3e5b5813e 100644 --- a/test/extensions/tracers/opencensus/config_test.cc +++ b/test/extensions/tracers/opencensus/config_test.cc @@ -50,6 +50,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerWithTypedConfig) { stackdriver_project_id: test_project_id zipkin_exporter_enabled: true zipkin_url: http://127.0.0.1:9411/api/v2/spans + incoming_trace_context: b3 incoming_trace_context: trace_context incoming_trace_context: grpc_trace_bin incoming_trace_context: cloud_trace_context diff --git a/test/extensions/tracers/opencensus/tracer_test.cc b/test/extensions/tracers/opencensus/tracer_test.cc index 2c7cf5695fec6..241f1d0a9a895 100644 --- a/test/extensions/tracers/opencensus/tracer_test.cc +++ b/test/extensions/tracers/opencensus/tracer_test.cc @@ -19,6 +19,7 @@ #include "gtest/gtest.h" #include "opencensus/trace/exporter/span_data.h" #include "opencensus/trace/exporter/span_exporter.h" +#include "opencensus/trace/propagation/b3.h" #include "opencensus/trace/propagation/cloud_trace_context.h" #include "opencensus/trace/propagation/grpc_trace_bin.h" #include "opencensus/trace/propagation/trace_context.h" @@ -161,78 +162,115 @@ TEST(OpenCensusTracerTest, Span) { } } -// Test that trace context propagation works. -TEST(OpenCensusTracerTest, PropagateTraceContext) { - registerSpanCatcher(); - // The test calls the helper with each kind of incoming context in turn. - auto helper = [](const std::string& header, const std::string& value) { - OpenCensusConfig oc_config; - NiceMock local_info; - oc_config.add_incoming_trace_context(OpenCensusConfig::NONE); - oc_config.add_incoming_trace_context(OpenCensusConfig::TRACE_CONTEXT); - oc_config.add_incoming_trace_context(OpenCensusConfig::GRPC_TRACE_BIN); - oc_config.add_incoming_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT); - oc_config.add_outgoing_trace_context(OpenCensusConfig::NONE); - oc_config.add_outgoing_trace_context(OpenCensusConfig::TRACE_CONTEXT); - oc_config.add_outgoing_trace_context(OpenCensusConfig::GRPC_TRACE_BIN); - oc_config.add_outgoing_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT); - std::unique_ptr driver(new OpenCensus::Driver(oc_config, local_info)); - NiceMock config; - Http::TestHeaderMapImpl request_headers{ - {":path", "/"}, - {":method", "GET"}, - {"x-request-id", "foo"}, - {header, value}, - }; - const std::string operation_name{"my_operation_2"}; - SystemTime start_time; - Http::TestHeaderMapImpl injected_headers; - { - Tracing::SpanPtr span = driver->startSpan(config, request_headers, operation_name, start_time, - {Tracing::Reason::Sampling, false}); - span->injectContext(injected_headers); - span->finishSpan(); - } +namespace { - // Retrieve SpanData from the OpenCensus trace exporter. - std::vector spans = getSpanCatcher()->catchSpans(); - ASSERT_EQ(1, spans.size()); - const auto& sd = spans[0]; - ENVOY_LOG_MISC(debug, "{}", sd.DebugString()); +using testing::PrintToString; - // Check contents. - EXPECT_TRUE(sd.has_remote_parent()); - EXPECT_EQ("6162636465666768", sd.parent_span_id().ToHex()); - EXPECT_EQ("404142434445464748494a4b4c4d4e4f", sd.context().trace_id().ToHex()); - EXPECT_TRUE(sd.context().trace_options().IsSampled()) - << "parent was sampled, child should be also"; - - // Check injectContext. - using Envoy::Http::LowerCaseString; - { - auto val = injected_headers.get(LowerCaseString("traceparent")); - ASSERT_NE(nullptr, val); - EXPECT_EQ(::opencensus::trace::propagation::ToTraceParentHeader(sd.context()), - val->value().getStringView()); - } - { - auto val = injected_headers.get(LowerCaseString("grpc-trace-bin")); - ASSERT_NE(nullptr, val); - std::string expected = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(sd.context()); - expected = Base64::encode(expected.data(), expected.size(), /*add_padding=*/false); - EXPECT_EQ(expected, val->value().getStringView()); - } - { - auto val = injected_headers.get(LowerCaseString("x-cloud-trace-context")); - ASSERT_NE(nullptr, val); - EXPECT_EQ(::opencensus::trace::propagation::ToCloudTraceContextHeader(sd.context()), - val->value().getStringView()); - } +MATCHER_P2(ContainHeader, header, expected_value, + "contains the header " + PrintToString(header) + " with value " + + PrintToString(expected_value)) { + const auto found_value = arg.get(Http::LowerCaseString(header)); + if (found_value == nullptr) { + return false; + } + return found_value->value().getStringView() == expected_value; +} + +// Given incoming headers, test that trace context propagation works and generates all the expected +// outgoing headers. +void testIncomingHeaders( + const std::initializer_list>& headers) { + registerSpanCatcher(); + OpenCensusConfig oc_config; + NiceMock local_info; + oc_config.add_incoming_trace_context(OpenCensusConfig::NONE); + oc_config.add_incoming_trace_context(OpenCensusConfig::B3); + oc_config.add_incoming_trace_context(OpenCensusConfig::TRACE_CONTEXT); + oc_config.add_incoming_trace_context(OpenCensusConfig::GRPC_TRACE_BIN); + oc_config.add_incoming_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT); + oc_config.add_outgoing_trace_context(OpenCensusConfig::NONE); + oc_config.add_outgoing_trace_context(OpenCensusConfig::B3); + oc_config.add_outgoing_trace_context(OpenCensusConfig::TRACE_CONTEXT); + oc_config.add_outgoing_trace_context(OpenCensusConfig::GRPC_TRACE_BIN); + oc_config.add_outgoing_trace_context(OpenCensusConfig::CLOUD_TRACE_CONTEXT); + std::unique_ptr driver(new OpenCensus::Driver(oc_config, local_info)); + NiceMock config; + Http::TestHeaderMapImpl request_headers{ + {":path", "/"}, + {":method", "GET"}, + {"x-request-id", "foo"}, }; + for (const auto& kv : headers) { + request_headers.addCopy(Http::LowerCaseString(kv.first), kv.second); + } + + const std::string operation_name{"my_operation_2"}; + SystemTime start_time; + Http::TestHeaderMapImpl injected_headers; + { + Tracing::SpanPtr span = driver->startSpan(config, request_headers, operation_name, start_time, + {Tracing::Reason::Sampling, false}); + span->injectContext(injected_headers); + span->finishSpan(); + } + + // Retrieve SpanData from the OpenCensus trace exporter. + std::vector spans = getSpanCatcher()->catchSpans(); + ASSERT_EQ(1, spans.size()); + const auto& sd = spans[0]; + ENVOY_LOG_MISC(debug, "{}", sd.DebugString()); + + // Check contents. + EXPECT_TRUE(sd.has_remote_parent()); + EXPECT_EQ("6162636465666768", sd.parent_span_id().ToHex()); + EXPECT_EQ("404142434445464748494a4b4c4d4e4f", sd.context().trace_id().ToHex()); + EXPECT_TRUE(sd.context().trace_options().IsSampled()) + << "parent was sampled, child should be also"; + + // Check injectContext. + // The SpanID is unpredictable so re-serialize context to check it. + const auto& ctx = sd.context(); + const auto& hdrs = injected_headers; + EXPECT_THAT(hdrs, ContainHeader("traceparent", + ::opencensus::trace::propagation::ToTraceParentHeader(ctx))); + { + std::string expected = ::opencensus::trace::propagation::ToGrpcTraceBinHeader(ctx); + expected = Base64::encode(expected.data(), expected.size(), /*add_padding=*/false); + EXPECT_THAT(hdrs, ContainHeader("grpc-trace-bin", expected)); + } + EXPECT_THAT(hdrs, + ContainHeader("x-cloud-trace-context", + ::opencensus::trace::propagation::ToCloudTraceContextHeader(ctx))); + EXPECT_THAT(hdrs, ContainHeader("x-b3-traceid", "404142434445464748494a4b4c4d4e4f")); + EXPECT_THAT( + hdrs, ContainHeader("x-b3-spanid", ::opencensus::trace::propagation::ToB3SpanIdHeader(ctx))); + EXPECT_THAT(hdrs, ContainHeader("x-b3-sampled", "1")); +} +} // namespace + +TEST(OpenCensusTracerTest, PropagateTraceParentContext) { + testIncomingHeaders({{"traceparent", "00-404142434445464748494a4b4c4d4e4f-6162636465666768-01"}}); +} + +TEST(OpenCensusTracerTest, PropagateGrpcTraceBinContext) { + testIncomingHeaders({{"grpc-trace-bin", "AABAQUJDREVGR0hJSktMTU5PAWFiY2RlZmdoAgE"}}); +} + +TEST(OpenCensusTracerTest, PropagateCloudTraceContext) { + testIncomingHeaders( + {{"x-cloud-trace-context", "404142434445464748494a4b4c4d4e4f/7017280452245743464;o=1"}}); +} + +TEST(OpenCensusTracerTest, PropagateB3Context) { + testIncomingHeaders({{"x-b3-traceid", "404142434445464748494a4b4c4d4e4f"}, + {"x-b3-spanid", "6162636465666768"}, + {"x-b3-sampled", "1"}}); +} - helper("traceparent", "00-404142434445464748494a4b4c4d4e4f-6162636465666768-01"); - helper("grpc-trace-bin", "AABAQUJDREVGR0hJSktMTU5PAWFiY2RlZmdoAgE"); - helper("x-cloud-trace-context", "404142434445464748494a4b4c4d4e4f/7017280452245743464;o=1"); +TEST(OpenCensusTracerTest, PropagateB3ContextWithDebugFlag) { + testIncomingHeaders({{"x-b3-traceid", "404142434445464748494a4b4c4d4e4f"}, + {"x-b3-spanid", "6162636465666768"}, + {"x-b3-flags", "1"}}); // Debug flag causes sampling. } namespace {