@@ -2204,3 +2204,146 @@ func runDisconnectScenario(t *testing.T, name, wantLabel string, action func(*st
22042204 t .Fatalf ("Metric verification failed for case %s: %v" , name , err )
22052205 }
22062206}
2207+
2208+ // TestRelayContextCollisionMetrics verifies that when an application acts as
2209+ // both a server and a client using the same context, the client metrics do not
2210+ // inherit or overwrite the server's telemetry metadata (e.g., grpc.method).
2211+ func (s ) TestRelayContextCollisionMetrics (t * testing.T ) {
2212+ backendMetricsOpts , _ := defaultMetricsOptions (t , nil )
2213+ backendServer := setupStubServer (t , backendMetricsOpts , nil )
2214+ backendServer .EmptyCallF = func (_ context.Context , _ * testpb.Empty ) (* testpb.Empty , error ) {
2215+ return nil , status .Error (codes .Unimplemented , "EmptyCall not implemented" )
2216+ }
2217+ defer backendServer .Stop ()
2218+
2219+ relayMetricsOpts , relayMetricsReader := defaultMetricsOptions (t , nil )
2220+ otelOpts := opentelemetry.Options {MetricsOptions : * relayMetricsOpts }
2221+
2222+ relayServer := & stubserver.StubServer {
2223+ UnaryCallF : func (ctx context.Context , _ * testpb.SimpleRequest ) (* testpb.SimpleResponse , error ) {
2224+ relayCC , err := grpc .NewClient (
2225+ backendServer .Address ,
2226+ grpc .WithTransportCredentials (insecure .NewCredentials ()),
2227+ opentelemetry .DialOption (otelOpts ),
2228+ )
2229+ if err != nil {
2230+ return nil , fmt .Errorf ("failed to create relay client: %v" , err )
2231+ }
2232+ defer relayCC .Close ()
2233+ client := testpb .NewTestServiceClient (relayCC )
2234+ _ , err = client .EmptyCall (ctx , & testpb.Empty {})
2235+ if status .Code (err ) != codes .Unimplemented {
2236+ t .Errorf ("Expected Unimplemented error, got: %v" , err )
2237+ }
2238+ return & testpb.SimpleResponse {}, nil
2239+ },
2240+ }
2241+ if err := relayServer .Start ([]grpc.ServerOption {opentelemetry .ServerOption (otelOpts )}, opentelemetry .DialOption (otelOpts )); err != nil {
2242+ t .Fatalf ("Failed to start relay server: %v" , err )
2243+ }
2244+ defer relayServer .Stop ()
2245+
2246+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
2247+ defer cancel ()
2248+
2249+ if _ , err := relayServer .Client .UnaryCall (ctx , & testpb.SimpleRequest {}); err != nil {
2250+ t .Fatalf ("Unexpected UnaryCall error: %v" , err )
2251+ }
2252+
2253+ // Verify Server Metric Identity is retained.
2254+ if err := checkMetricWithMethod (ctx , relayMetricsReader , "grpc.server.call.started" , "grpc.testing.TestService/UnaryCall" ); err != nil {
2255+ t .Fatal (err )
2256+ }
2257+
2258+ // Verify Client Metric Identity correctly resolved to "grpc.testing.TestService/EmptyCall".
2259+ if err := checkMetricWithMethod (ctx , relayMetricsReader , "grpc.client.attempt.started" , "grpc.testing.TestService/EmptyCall" ); err != nil {
2260+ t .Fatal (err )
2261+ }
2262+ }
2263+
2264+ // TestRelayContextCollisionTracing verifies that span context is correctly
2265+ // propagated from incoming server requests to outgoing client requests without
2266+ // the client span accidentally adopting the server's identity or breaking the
2267+ // trace chain.
2268+ func (s ) TestRelayContextCollisionTracing (t * testing.T ) {
2269+ backendTraceOpts , _ := defaultTraceOptions (t )
2270+ backendServer := setupStubServer (t , nil , backendTraceOpts )
2271+ backendServer .EmptyCallF = func (_ context.Context , _ * testpb.Empty ) (* testpb.Empty , error ) {
2272+ return nil , status .Error (codes .Unimplemented , "EmptyCall not implemented" )
2273+ }
2274+ defer backendServer .Stop ()
2275+
2276+ relayTraceOpts , relayTraceExporter := defaultTraceOptions (t )
2277+ otelOpts := opentelemetry.Options {TraceOptions : * relayTraceOpts }
2278+
2279+ relayServer := & stubserver.StubServer {
2280+ UnaryCallF : func (ctx context.Context , _ * testpb.SimpleRequest ) (* testpb.SimpleResponse , error ) {
2281+ relayCC , err := grpc .NewClient (
2282+ backendServer .Address ,
2283+ grpc .WithTransportCredentials (insecure .NewCredentials ()),
2284+ opentelemetry .DialOption (otelOpts ),
2285+ )
2286+ if err != nil {
2287+ return nil , fmt .Errorf ("failed to create relay client: %v" , err )
2288+ }
2289+ defer relayCC .Close ()
2290+ client := testpb .NewTestServiceClient (relayCC )
2291+ _ , err = client .EmptyCall (ctx , & testpb.Empty {})
2292+ if status .Code (err ) != codes .Unimplemented {
2293+ t .Errorf ("Expected Unimplemented error, got: %v" , err )
2294+ }
2295+ return & testpb.SimpleResponse {}, nil
2296+ },
2297+ }
2298+ if err := relayServer .Start ([]grpc.ServerOption {opentelemetry .ServerOption (otelOpts )}, opentelemetry .DialOption (otelOpts )); err != nil {
2299+ t .Fatalf ("Failed to start relay server: %v" , err )
2300+ }
2301+ defer relayServer .Stop ()
2302+
2303+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
2304+ defer cancel ()
2305+
2306+ _ , _ = relayServer .Client .UnaryCall (ctx , & testpb.SimpleRequest {})
2307+
2308+ wantSpans := []traceSpanInfo {
2309+ {name : "Recv." , spanKind : "server" },
2310+ {name : "Sent.grpc.testing.TestService.EmptyCall" , spanKind : "client" },
2311+ }
2312+ spans , err := waitForTraceSpans (ctx , relayTraceExporter , wantSpans )
2313+ if err != nil {
2314+ t .Fatalf ("Failed to wait for spans: %v" , err )
2315+ }
2316+
2317+ var srvTraceID , cliTraceID oteltrace.TraceID
2318+ for _ , span := range spans {
2319+ if span .Name == "Recv." && span .SpanKind == oteltrace .SpanKindServer {
2320+ srvTraceID = span .SpanContext .TraceID ()
2321+ }
2322+ if span .Name == "Sent.grpc.testing.TestService.EmptyCall" && span .SpanKind == oteltrace .SpanKindClient {
2323+ cliTraceID = span .SpanContext .TraceID ()
2324+ }
2325+ }
2326+ if ! srvTraceID .IsValid () || ! cliTraceID .IsValid () {
2327+ t .Fatalf ("Invalid trace IDs found. Server: %s, Client: %s" , srvTraceID , cliTraceID )
2328+ }
2329+
2330+ if srvTraceID != cliTraceID {
2331+ t .Errorf ("Trace continuity broken: Server TraceID %s != Client TraceID %s" , srvTraceID , cliTraceID )
2332+ }
2333+ }
2334+
2335+ // checkMetricWithMethod verifies that a metric with the specified name contains
2336+ // a data point matching the target grpc.method. It does not poll.
2337+ func checkMetricWithMethod (ctx context.Context , reader * metric.ManualReader , metricName , method string ) error {
2338+ metrics := metricsDataFromReader (ctx , reader )
2339+ if m , ok := metrics [metricName ]; ok {
2340+ if sum , ok := m .Data .(metricdata.Sum [int64 ]); ok {
2341+ for _ , dp := range sum .DataPoints {
2342+ if val , ok := dp .Attributes .Value ("grpc.method" ); ok && val .AsString () == method {
2343+ return nil
2344+ }
2345+ }
2346+ }
2347+ }
2348+ return fmt .Errorf ("metric %q with method %q not found" , metricName , method )
2349+ }
0 commit comments