[CSM] Fix CSM Observability for trailers-only response (#36413)

Before this change, on a trailers-only response, Metadata Exchange needed by CSM would just be dropped, and hence CSM labels would not be seen.

Changes -
* OTel call attempt tracer populates labels from trailers if it's a trailers-only response.
* HTTP2 layer propagates the Metadata Exchange field from headers to trailers for trailers-only responses.
* Add a test to make sure that retries continue to work when Metadata Exchange is enabled and a trailers-only response is sent. (This verifies that a trailers-only response remains a trailers-only response.)

Closes #36413

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/36413 from yashykt:MetadataExchangeInTrailers e7d202685e
PiperOrigin-RevId: 630144618
pull/36512/head
Yash Tibrewal 12 months ago committed by Copybara-Service
parent ffe73d230c
commit 108ee944df
  1. 47
      src/core/ext/transport/chttp2/transport/writing.cc
  2. 29
      src/core/lib/transport/metadata_batch.h
  3. 38
      src/cpp/ext/otel/otel_client_call_tracer.cc
  4. 3
      src/cpp/ext/otel/otel_client_call_tracer.h
  5. 101
      test/cpp/ext/csm/metadata_exchange_test.cc
  6. 3
      test/cpp/ext/otel/otel_test_library.cc
  7. 6
      test/cpp/ext/otel/otel_test_library.h

@ -561,14 +561,6 @@ class StreamWriteContext {
grpc_chttp2_encode_data(s_->id, &s_->flow_controlled_buffer, 0, true,
&s_->stats.outgoing, t_->outbuf.c_slice_buffer());
} else {
if (send_status_.has_value()) {
s_->send_trailing_metadata->Set(grpc_core::HttpStatusMetadata(),
*send_status_);
}
if (send_content_type_.has_value()) {
s_->send_trailing_metadata->Set(grpc_core::ContentTypeMetadata(),
*send_content_type_);
}
t_->hpack_compressor.EncodeHeaders(
grpc_core::HPackCompressor::EncodeHeaderOptions{
s_->id, true, t_->settings.peer().allow_true_binary_metadata(),
@ -588,15 +580,39 @@ class StreamWriteContext {
bool stream_became_writable() { return stream_became_writable_; }
private:
class TrailersOnlyMetadataEncoder {
public:
explicit TrailersOnlyMetadataEncoder(grpc_metadata_batch* trailing_md)
: trailing_md_(trailing_md) {}
template <typename Which, typename Value>
void Encode(Which which, Value value) {
if (Which::kTransferOnTrailersOnly) {
trailing_md_->Set(which, value);
}
}
template <typename Which>
void Encode(Which which, const grpc_core::Slice& value) {
if (Which::kTransferOnTrailersOnly) {
trailing_md_->Set(which, value.Ref());
}
}
// Non-grpc metadata should not be transferred.
void Encode(const grpc_core::Slice&, const grpc_core::Slice&) {}
private:
grpc_metadata_batch* trailing_md_;
};
void ConvertInitialMetadataToTrailingMetadata() {
GRPC_CHTTP2_IF_TRACING(
gpr_log(GPR_INFO, "not sending initial_metadata (Trailers-Only)"));
// When sending Trailers-Only, we need to move the :status and
// content-type headers to the trailers.
send_status_ =
s_->send_initial_metadata->get(grpc_core::HttpStatusMetadata());
send_content_type_ =
s_->send_initial_metadata->get(grpc_core::ContentTypeMetadata());
// When sending Trailers-Only, we need to move metadata from headers to
// trailers.
TrailersOnlyMetadataEncoder encoder(s_->send_trailing_metadata);
s_->send_initial_metadata->Encode(&encoder);
}
void SentLastFrame() {
@ -629,9 +645,6 @@ class StreamWriteContext {
grpc_chttp2_transport* const t_;
grpc_chttp2_stream* const s_;
bool stream_became_writable_ = false;
absl::optional<uint32_t> send_status_;
absl::optional<grpc_core::ContentTypeMetadata::ValueType> send_content_type_ =
{};
};
} // namespace

@ -77,6 +77,7 @@ size_t EncodedSizeOfKey(Key, const typename Key::ValueType& value) {
// should not need to.
struct GrpcTimeoutMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using ValueType = Timestamp;
using MementoType = Duration;
using CompressionTraits = TimeoutCompressor;
@ -93,6 +94,7 @@ struct GrpcTimeoutMetadata {
// TE metadata trait.
struct TeMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
// HTTP2 says that TE can either be empty or "trailers".
// Empty means this trait is not included, "trailers" means kTrailers, and
// kInvalid is used to remember an invalid value.
@ -122,6 +124,7 @@ inline size_t EncodedSizeOfKey(TeMetadata, TeMetadata::ValueType x) {
// content-type metadata trait.
struct ContentTypeMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = true;
// gRPC says that content-type can be application/grpc[;something]
// Core has only ever verified the prefix.
// IF we want to start verifying more, we can expand this type.
@ -150,6 +153,7 @@ struct ContentTypeMetadata {
// scheme metadata trait.
struct HttpSchemeMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
enum ValueType : uint8_t {
kHttp,
kHttps,
@ -179,6 +183,7 @@ size_t EncodedSizeOfKey(HttpSchemeMetadata, HttpSchemeMetadata::ValueType x);
// method metadata trait.
struct HttpMethodMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
enum ValueType : uint8_t {
kPost,
kGet,
@ -227,6 +232,7 @@ struct CompressionAlgorithmBasedMetadata {
// grpc-encoding metadata trait.
struct GrpcEncodingMetadata : public CompressionAlgorithmBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits =
SmallIntegralValuesCompressor<GRPC_COMPRESS_ALGORITHMS_COUNT>;
static absl::string_view key() { return "grpc-encoding"; }
@ -235,6 +241,7 @@ struct GrpcEncodingMetadata : public CompressionAlgorithmBasedMetadata {
// grpc-internal-encoding-request metadata trait.
struct GrpcInternalEncodingRequest : public CompressionAlgorithmBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "grpc-internal-encoding-request"; }
};
@ -242,6 +249,7 @@ struct GrpcInternalEncodingRequest : public CompressionAlgorithmBasedMetadata {
// grpc-accept-encoding metadata trait.
struct GrpcAcceptEncodingMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
static absl::string_view key() { return "grpc-accept-encoding"; }
using ValueType = CompressionAlgorithmSet;
using MementoType = ValueType;
@ -260,6 +268,7 @@ struct GrpcAcceptEncodingMetadata {
// user-agent metadata trait.
struct UserAgentMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = StableValueCompressor;
static absl::string_view key() { return "user-agent"; }
};
@ -267,6 +276,7 @@ struct UserAgentMetadata : public SimpleSliceBasedMetadata {
// grpc-message metadata trait.
struct GrpcMessageMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "grpc-message"; }
};
@ -274,6 +284,7 @@ struct GrpcMessageMetadata : public SimpleSliceBasedMetadata {
// host metadata trait.
struct HostMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "host"; }
};
@ -281,6 +292,7 @@ struct HostMetadata : public SimpleSliceBasedMetadata {
// endpoint-load-metrics-bin metadata trait.
struct EndpointLoadMetricsBinMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "endpoint-load-metrics-bin"; }
};
@ -288,6 +300,7 @@ struct EndpointLoadMetricsBinMetadata : public SimpleSliceBasedMetadata {
// grpc-server-stats-bin metadata trait.
struct GrpcServerStatsBinMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "grpc-server-stats-bin"; }
};
@ -295,6 +308,7 @@ struct GrpcServerStatsBinMetadata : public SimpleSliceBasedMetadata {
// grpc-trace-bin metadata trait.
struct GrpcTraceBinMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = FrequentKeyWithNoValueCompressionCompressor;
static absl::string_view key() { return "grpc-trace-bin"; }
};
@ -302,6 +316,7 @@ struct GrpcTraceBinMetadata : public SimpleSliceBasedMetadata {
// grpc-tags-bin metadata trait.
struct GrpcTagsBinMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = FrequentKeyWithNoValueCompressionCompressor;
static absl::string_view key() { return "grpc-tags-bin"; }
};
@ -309,6 +324,7 @@ struct GrpcTagsBinMetadata : public SimpleSliceBasedMetadata {
// XEnvoyPeerMetadata
struct XEnvoyPeerMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = true;
using CompressionTraits = StableValueCompressor;
static absl::string_view key() { return "x-envoy-peer-metadata"; }
};
@ -316,6 +332,7 @@ struct XEnvoyPeerMetadata : public SimpleSliceBasedMetadata {
// :authority metadata trait.
struct HttpAuthorityMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = SmallSetOfValuesCompressor;
static absl::string_view key() { return ":authority"; }
};
@ -323,6 +340,7 @@ struct HttpAuthorityMetadata : public SimpleSliceBasedMetadata {
// :path metadata trait.
struct HttpPathMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = SmallSetOfValuesCompressor;
static absl::string_view key() { return ":path"; }
};
@ -357,6 +375,7 @@ struct SimpleIntBasedMetadata : public SimpleIntBasedMetadataBase<Int> {
struct GrpcStatusMetadata
: public SimpleIntBasedMetadata<grpc_status_code, GRPC_STATUS_UNKNOWN> {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = SmallIntegralValuesCompressor<16>;
static absl::string_view key() { return "grpc-status"; }
};
@ -365,6 +384,7 @@ struct GrpcStatusMetadata
struct GrpcPreviousRpcAttemptsMetadata
: public SimpleIntBasedMetadata<uint32_t, 0> {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "grpc-previous-rpc-attempts"; }
};
@ -372,6 +392,7 @@ struct GrpcPreviousRpcAttemptsMetadata
// grpc-retry-pushback-ms metadata trait.
struct GrpcRetryPushbackMsMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
static absl::string_view key() { return "grpc-retry-pushback-ms"; }
using ValueType = Duration;
using MementoType = Duration;
@ -389,6 +410,7 @@ struct GrpcRetryPushbackMsMetadata {
// TODO(ctiller): consider moving to uint16_t
struct HttpStatusMetadata : public SimpleIntBasedMetadata<uint32_t, 0> {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = true;
using CompressionTraits = HttpStatusCompressor;
static absl::string_view key() { return ":status"; }
};
@ -399,6 +421,7 @@ class GrpcLbClientStats;
struct GrpcLbClientStatsMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
static absl::string_view key() { return "grpclb_client_stats"; }
using ValueType = GrpcLbClientStats*;
using MementoType = ValueType;
@ -423,6 +446,7 @@ inline size_t EncodedSizeOfKey(GrpcLbClientStatsMetadata,
// lb-token metadata
struct LbTokenMetadata : public SimpleSliceBasedMetadata {
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
using CompressionTraits = NoCompressionCompressor;
static absl::string_view key() { return "lb-token"; }
};
@ -430,6 +454,7 @@ struct LbTokenMetadata : public SimpleSliceBasedMetadata {
// lb-cost-bin metadata
struct LbCostBinMetadata {
static constexpr bool kRepeatable = true;
static constexpr bool kTransferOnTrailersOnly = false;
static absl::string_view key() { return "lb-cost-bin"; }
struct ValueType {
double cost;
@ -451,6 +476,7 @@ struct LbCostBinMetadata {
struct GrpcStreamNetworkState {
static absl::string_view DebugKey() { return "GrpcStreamNetworkState"; }
static constexpr bool kRepeatable = false;
static constexpr bool kTransferOnTrailersOnly = false;
enum ValueType : uint8_t {
kNotSentOnWire,
kNotSeenByServer,
@ -1149,6 +1175,9 @@ MetadataValueAsSlice(typename Which::ValueType value) {
// struct GrpcXyzMetadata {
// // Can this metadata field be repeated?
// static constexpr bool kRepeatable = ...;
// // Should this metadata be transferred from server headers to trailers on
// // Trailers-Only response?
// static constexpr bool kTransferOnTrailersOnly = ...;
// // The type that's stored on MetadataBatch
// using ValueType = ...;
// // The type that's stored in compression/decompression tables

@ -88,17 +88,13 @@ OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::CallAttemptTracer(
void OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::
RecordReceivedInitialMetadata(grpc_metadata_batch* recv_initial_metadata) {
parent_->scope_config_->active_plugin_options_view().ForEach(
[&](const InternalOpenTelemetryPluginOption& plugin_option,
size_t /*index*/) {
auto* labels_injector = plugin_option.labels_injector();
if (labels_injector != nullptr) {
injected_labels_from_plugin_options_.push_back(
labels_injector->GetLabels(recv_initial_metadata));
}
return true;
},
parent_->otel_plugin_);
if (recv_initial_metadata != nullptr &&
recv_initial_metadata->get(grpc_core::GrpcTrailersOnly())
.value_or(false)) {
is_trailers_only_ = true;
return;
}
PopulateLabelInjectors(recv_initial_metadata);
}
void OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::
@ -143,8 +139,11 @@ void OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::
void OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::
RecordReceivedTrailingMetadata(
absl::Status status, grpc_metadata_batch* /*recv_trailing_metadata*/,
absl::Status status, grpc_metadata_batch* recv_trailing_metadata,
const grpc_transport_stream_stats* transport_stream_stats) {
if (is_trailers_only_) {
PopulateLabelInjectors(recv_trailing_metadata);
}
std::array<std::pair<absl::string_view, absl::string_view>, 3>
additional_labels = {
{{OpenTelemetryMethodKey(), parent_->MethodForStats()},
@ -214,6 +213,21 @@ void OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::SetOptionalLabel(
optional_labels_[static_cast<size_t>(key)] = std::move(value);
}
void OpenTelemetryPlugin::ClientCallTracer::CallAttemptTracer::
PopulateLabelInjectors(grpc_metadata_batch* metadata) {
parent_->scope_config_->active_plugin_options_view().ForEach(
[&](const InternalOpenTelemetryPluginOption& plugin_option,
size_t /*index*/) {
auto* labels_injector = plugin_option.labels_injector();
if (labels_injector != nullptr) {
injected_labels_from_plugin_options_.push_back(
labels_injector->GetLabels(metadata));
}
return true;
},
parent_->otel_plugin_);
}
//
// OpenTelemetryPlugin::ClientCallTracer
//

@ -95,6 +95,8 @@ class OpenTelemetryPlugin::ClientCallTracer
grpc_core::RefCountedStringValue value) override;
private:
void PopulateLabelInjectors(grpc_metadata_batch* metadata);
const ClientCallTracer* parent_;
const bool arena_allocated_;
// Start time (for measuring latency).
@ -106,6 +108,7 @@ class OpenTelemetryPlugin::ClientCallTracer
optional_labels_;
std::vector<std::unique_ptr<LabelsIterable>>
injected_labels_from_plugin_options_;
bool is_trailers_only_ = false;
};
ClientCallTracer(

@ -157,12 +157,7 @@ class MetadataExchangeTest
: public OpenTelemetryPluginEnd2EndTest,
public ::testing::WithParamInterface<TestScenario> {
protected:
void Init(
const std::vector<absl::string_view>& metric_names,
bool enable_client_side_injector = true,
std::map<grpc_core::ClientCallTracer::CallAttemptTracer::OptionalLabelKey,
grpc_core::RefCountedStringValue>
labels_to_inject = {}) {
void Init(Options options, bool enable_client_side_injector = true) {
const char* kBootstrap =
"{\"node\": {\"id\": "
"\"projects/1234567890/networks/mesh:mesh-id/nodes/"
@ -182,11 +177,9 @@ class MetadataExchangeTest
break;
}
OpenTelemetryPluginEnd2EndTest::Init(std::move(
Options()
.set_metric_names(metric_names)
options
.add_plugin_option(std::make_unique<MeshLabelsPluginOption>(
GetParam().GetTestResource().GetAttributes()))
.set_labels_to_inject(std::move(labels_to_inject))
.set_channel_scope_filter(
[enable_client_side_injector](
const OpenTelemetryPluginBuilder::ChannelScope& /*scope*/) {
@ -284,8 +277,9 @@ class MetadataExchangeTest
// Verify that grpc.client.attempt.started does not get service mesh attributes
TEST_P(MetadataExchangeTest, ClientAttemptStarted) {
Init(/*metric_names=*/{
grpc::OpenTelemetryPluginBuilder::kClientAttemptStartedInstrumentName});
Init(std::move(
Options().set_metric_names({grpc::OpenTelemetryPluginBuilder::
kClientAttemptStartedInstrumentName})));
SendRPC();
const char* kMetricName = "grpc.client.attempt.started";
auto data = ReadCurrentMetricsData(
@ -308,8 +302,9 @@ TEST_P(MetadataExchangeTest, ClientAttemptStarted) {
}
TEST_P(MetadataExchangeTest, ClientAttemptDuration) {
Init(/*metric_names=*/{
grpc::OpenTelemetryPluginBuilder::kClientAttemptDurationInstrumentName});
Init(std::move(
Options().set_metric_names({grpc::OpenTelemetryPluginBuilder::
kClientAttemptDurationInstrumentName})));
SendRPC();
const char* kMetricName = "grpc.client.attempt.duration";
auto data = ReadCurrentMetricsData(
@ -333,9 +328,8 @@ TEST_P(MetadataExchangeTest, ClientAttemptDuration) {
// Verify that grpc.server.call.started does not get service mesh attributes
TEST_P(MetadataExchangeTest, ServerCallStarted) {
Init(
/*metric_names=*/{
grpc::OpenTelemetryPluginBuilder::kServerCallStartedInstrumentName});
Init(std::move(Options().set_metric_names(
{grpc::OpenTelemetryPluginBuilder::kServerCallStartedInstrumentName})));
SendRPC();
const char* kMetricName = "grpc.server.call.started";
auto data = ReadCurrentMetricsData(
@ -354,9 +348,8 @@ TEST_P(MetadataExchangeTest, ServerCallStarted) {
}
TEST_P(MetadataExchangeTest, ServerCallDuration) {
Init(
/*metric_names=*/{
grpc::OpenTelemetryPluginBuilder::kServerCallDurationInstrumentName});
Init(std::move(Options().set_metric_names(
{grpc::OpenTelemetryPluginBuilder::kServerCallDurationInstrumentName})));
SendRPC();
const char* kMetricName = "grpc.server.call.duration";
auto data = ReadCurrentMetricsData(
@ -378,10 +371,10 @@ TEST_P(MetadataExchangeTest, ServerCallDuration) {
// Test that the server records unknown when the client does not send metadata
TEST_P(MetadataExchangeTest, ClientDoesNotSendMetadata) {
Init(
/*metric_names=*/{grpc::OpenTelemetryPluginBuilder::
kServerCallDurationInstrumentName},
/*enable_client_side_injector=*/false);
Init(std::move(
Options().set_metric_names({grpc::OpenTelemetryPluginBuilder::
kServerCallDurationInstrumentName})),
/*enable_client_side_injector=*/false);
SendRPC();
const char* kMetricName = "grpc.server.call.duration";
auto data = ReadCurrentMetricsData(
@ -407,14 +400,15 @@ TEST_P(MetadataExchangeTest, ClientDoesNotSendMetadata) {
}
TEST_P(MetadataExchangeTest, VerifyCsmServiceLabels) {
Init(/*metric_names=*/{grpc::OpenTelemetryPluginBuilder::
kClientAttemptDurationInstrumentName},
/*enable_client_side_injector=*/true,
// Injects CSM service labels to be recorded in the call.
{{OptionalLabelKey::kXdsServiceName,
grpc_core::RefCountedStringValue("myservice")},
{OptionalLabelKey::kXdsServiceNamespace,
grpc_core::RefCountedStringValue("mynamespace")}});
Init(std::move(
Options()
.set_metric_names({grpc::OpenTelemetryPluginBuilder::
kClientAttemptDurationInstrumentName})
.set_labels_to_inject(
{{OptionalLabelKey::kXdsServiceName,
grpc_core::RefCountedStringValue("myservice")},
{OptionalLabelKey::kXdsServiceNamespace,
grpc_core::RefCountedStringValue("mynamespace")}})));
SendRPC();
const char* kMetricName = "grpc.client.attempt.duration";
auto data = ReadCurrentMetricsData(
@ -430,6 +424,51 @@ TEST_P(MetadataExchangeTest, VerifyCsmServiceLabels) {
"mynamespace");
}
TEST_P(MetadataExchangeTest, Retries) {
Init(std::move(
Options()
.set_metric_names({grpc::OpenTelemetryPluginBuilder::
kClientAttemptDurationInstrumentName})
.set_service_config(
"{\n"
" \"methodConfig\": [ {\n"
" \"name\": [\n"
" { \"service\": \"grpc.testing.EchoTestService\" }\n"
" ],\n"
" \"retryPolicy\": {\n"
" \"maxAttempts\": 3,\n"
" \"initialBackoff\": \"0.1s\",\n"
" \"maxBackoff\": \"120s\",\n"
" \"backoffMultiplier\": 1,\n"
" \"retryableStatusCodes\": [ \"ABORTED\" ]\n"
" }\n"
" } ]\n"
"}")));
EchoRequest request;
request.mutable_param()->mutable_expected_error()->set_code(
StatusCode::ABORTED);
EchoResponse response;
grpc::ClientContext context;
grpc::Status status = stub_->Echo(&context, request, &response);
const char* kMetricName = "grpc.client.attempt.duration";
auto data = ReadCurrentMetricsData(
[&](const absl::flat_hash_map<
std::string,
std::vector<opentelemetry::sdk::metrics::PointDataAttributes>>&
data) {
return !data.contains(kMetricName) ||
absl::get<opentelemetry::sdk::metrics::HistogramPointData>(
data.at(kMetricName)[0].point_data)
.count_ != 3;
});
ASSERT_EQ(absl::get<opentelemetry::sdk::metrics::HistogramPointData>(
data.at(kMetricName)[0].point_data)
.count_,
3);
VerifyServiceMeshAttributes(data.at(kMetricName)[0].attributes,
/*is_client=*/true);
}
// Creates a serialized slice with labels for metadata exchange based on \a
// resource.
grpc_core::Slice RemoteMetadataSliceFromResource(

@ -148,6 +148,9 @@ void OpenTelemetryPluginEnd2EndTest::Init(Options config) {
});
channel_args.SetPointer(GRPC_ARG_LABELS_TO_INJECT, &labels_to_inject_);
}
if (!config.service_config.empty()) {
channel_args.SetString(GRPC_ARG_SERVICE_CONFIG, config.service_config);
}
reader_ = BuildAndRegisterOpenTelemetryPlugin(std::move(config));
grpc_init();
grpc::ServerBuilder builder;

@ -86,6 +86,11 @@ class OpenTelemetryPluginEnd2EndTest : public ::testing::Test {
return *this;
}
Options& set_service_config(std::string svc_cfg) {
service_config = std::move(svc_cfg);
return *this;
}
Options& set_channel_scope_filter(
absl::AnyInvocable<bool(
const OpenTelemetryPluginBuilder::ChannelScope& /*scope*/) const>
@ -138,6 +143,7 @@ class OpenTelemetryPluginEnd2EndTest : public ::testing::Test {
std::map<grpc_core::ClientCallTracer::CallAttemptTracer::OptionalLabelKey,
grpc_core::RefCountedStringValue>
labels_to_inject;
std::string service_config;
absl::AnyInvocable<bool(
const OpenTelemetryPluginBuilder::ChannelScope& /*scope*/) const>
channel_scope_filter;

Loading…
Cancel
Save