@@ -21,6 +21,7 @@ import (
21
21
"fmt"
22
22
"io"
23
23
"slices"
24
+ "strconv"
24
25
"testing"
25
26
"time"
26
27
@@ -48,9 +49,11 @@ import (
48
49
"go.opentelemetry.io/otel/sdk/trace"
49
50
"go.opentelemetry.io/otel/sdk/trace/tracetest"
50
51
"google.golang.org/grpc"
52
+ "google.golang.org/grpc/codes"
51
53
"google.golang.org/grpc/credentials/insecure"
52
54
"google.golang.org/grpc/encoding/gzip"
53
55
experimental "google.golang.org/grpc/experimental/opentelemetry"
56
+ "google.golang.org/grpc/internal"
54
57
"google.golang.org/grpc/internal/grpcsync"
55
58
"google.golang.org/grpc/internal/grpctest"
56
59
"google.golang.org/grpc/internal/stubserver"
@@ -59,9 +62,13 @@ import (
59
62
setup "google.golang.org/grpc/internal/testutils/xds/e2e/setup"
60
63
testgrpc "google.golang.org/grpc/interop/grpc_testing"
61
64
testpb "google.golang.org/grpc/interop/grpc_testing"
65
+ "google.golang.org/grpc/metadata"
62
66
"google.golang.org/grpc/orca"
67
+ "google.golang.org/grpc/resolver"
68
+ "google.golang.org/grpc/resolver/manual"
63
69
"google.golang.org/grpc/stats/opentelemetry"
64
70
"google.golang.org/grpc/stats/opentelemetry/internal/testutils"
71
+ "google.golang.org/grpc/status"
65
72
)
66
73
67
74
var defaultTestTimeout = 5 * time .Second
@@ -1560,3 +1567,168 @@ func (s) TestRPCSpanErrorStatus(t *testing.T) {
1560
1567
t .Fatalf ("got rpc error %s, want %s" , spans [0 ].Status .Description , rpcErrorMsg )
1561
1568
}
1562
1569
}
1570
+
1571
+ const delayedResolutionEventName = "Delayed name resolution complete"
1572
+
1573
+ // TestTraceSpan_WithRetriesAndNameResolutionDelay verifies that
1574
+ // "Delayed name resolution complete" event is recorded in the call trace span
1575
+ // only once if any of the retry attempt encountered a delay in name resolution
1576
+ func (s ) TestTraceSpan_WithRetriesAndNameResolutionDelay (t * testing.T ) {
1577
+ tests := []struct {
1578
+ name string
1579
+ setupStub func () * stubserver.StubServer
1580
+ doCall func (context.Context , testgrpc.TestServiceClient ) error
1581
+ spanName string
1582
+ }{
1583
+ {
1584
+ name : "unary" ,
1585
+ setupStub : func () * stubserver.StubServer {
1586
+ return & stubserver.StubServer {
1587
+ UnaryCallF : func (ctx context.Context , in * testpb.SimpleRequest ) (* testpb.SimpleResponse , error ) {
1588
+ md , _ := metadata .FromIncomingContext (ctx )
1589
+ headerAttempts := 0
1590
+ if h := md ["grpc-previous-rpc-attempts" ]; len (h ) > 0 {
1591
+ headerAttempts , _ = strconv .Atoi (h [0 ])
1592
+ }
1593
+ if headerAttempts < 2 {
1594
+ return nil , status .Errorf (codes .Unavailable , "retry (%d)" , headerAttempts )
1595
+ }
1596
+ return & testpb.SimpleResponse {}, nil
1597
+ },
1598
+ }
1599
+ },
1600
+ doCall : func (ctx context.Context , client testgrpc.TestServiceClient ) error {
1601
+ _ , err := client .UnaryCall (ctx , & testpb.SimpleRequest {})
1602
+ return err
1603
+ },
1604
+ spanName : "grpc.testing.TestService.UnaryCall" ,
1605
+ },
1606
+ {
1607
+ name : "streaming" ,
1608
+ setupStub : func () * stubserver.StubServer {
1609
+ return & stubserver.StubServer {
1610
+ FullDuplexCallF : func (stream testgrpc.TestService_FullDuplexCallServer ) error {
1611
+ md , _ := metadata .FromIncomingContext (stream .Context ())
1612
+ headerAttempts := 0
1613
+ if h := md ["grpc-previous-rpc-attempts" ]; len (h ) > 0 {
1614
+ headerAttempts , _ = strconv .Atoi (h [0 ])
1615
+ }
1616
+ if headerAttempts < 2 {
1617
+ return status .Errorf (codes .Unavailable , "retry (%d)" , headerAttempts )
1618
+ }
1619
+ for {
1620
+ _ , err := stream .Recv ()
1621
+ if err == io .EOF {
1622
+ return nil
1623
+ }
1624
+ if err != nil {
1625
+ return err
1626
+ }
1627
+ }
1628
+ },
1629
+ }
1630
+ },
1631
+ doCall : func (ctx context.Context , client testgrpc.TestServiceClient ) error {
1632
+ stream , err := client .FullDuplexCall (ctx )
1633
+ if err != nil {
1634
+ return err
1635
+ }
1636
+ if err := stream .Send (& testpb.StreamingOutputCallRequest {}); err != nil {
1637
+ return err
1638
+ }
1639
+ if err := stream .CloseSend (); err != nil {
1640
+ return err
1641
+ }
1642
+ _ , err = stream .Recv ()
1643
+ if err != nil && err != io .EOF {
1644
+ return err
1645
+ }
1646
+ return nil
1647
+ },
1648
+ spanName : "grpc.testing.TestService.FullDuplexCall" ,
1649
+ },
1650
+ }
1651
+
1652
+ for _ , tt := range tests {
1653
+ t .Run (tt .name , func (t * testing.T ) {
1654
+ resolutionWait := grpcsync .NewEvent ()
1655
+ prevHook := internal .NewStreamWaitingForResolver
1656
+ internal .NewStreamWaitingForResolver = func () { resolutionWait .Fire () }
1657
+ defer func () { internal .NewStreamWaitingForResolver = prevHook }()
1658
+
1659
+ mo , _ := defaultMetricsOptions (t , nil )
1660
+ to , exporter := defaultTraceOptions (t )
1661
+ rb := manual .NewBuilderWithScheme ("delayed" )
1662
+ ss := tt .setupStub ()
1663
+ opts := opentelemetry.Options {MetricsOptions : * mo , TraceOptions : * to }
1664
+ if err := ss .Start ([]grpc.ServerOption {opentelemetry .ServerOption (opts )}); err != nil {
1665
+ t .Fatal (err )
1666
+ }
1667
+ defer ss .Stop ()
1668
+
1669
+ retryPolicy := `{
1670
+ "methodConfig": [{
1671
+ "name": [{"service": "grpc.testing.TestService"}],
1672
+ "retryPolicy": {
1673
+ "maxAttempts": 3,
1674
+ "initialBackoff": "0.05s",
1675
+ "maxBackoff": "0.2s",
1676
+ "backoffMultiplier": 1.0,
1677
+ "retryableStatusCodes": ["UNAVAILABLE"]
1678
+ }
1679
+ }]
1680
+ }`
1681
+ cc , err := grpc .NewClient (
1682
+ rb .Scheme ()+ ":///test.server" ,
1683
+ grpc .WithTransportCredentials (insecure .NewCredentials ()),
1684
+ grpc .WithResolvers (rb ),
1685
+ opentelemetry .DialOption (opts ),
1686
+ grpc .WithDefaultServiceConfig (retryPolicy ),
1687
+ )
1688
+ if err != nil {
1689
+ t .Fatal (err )
1690
+ }
1691
+ defer cc .Close ()
1692
+
1693
+ client := testgrpc .NewTestServiceClient (cc )
1694
+ ctx , cancel := context .WithTimeout (context .Background (), defaultTestTimeout )
1695
+ defer cancel ()
1696
+
1697
+ go func () {
1698
+ <- resolutionWait .Done ()
1699
+ rb .UpdateState (resolver.State {Addresses : []resolver.Address {{Addr : ss .Address }}})
1700
+ }()
1701
+ if err := tt .doCall (ctx , client ); err != nil {
1702
+ t .Fatalf ("%s call failed: %v" , tt .name , err )
1703
+ }
1704
+
1705
+ wantSpanInfo := traceSpanInfo {
1706
+ name : tt .spanName ,
1707
+ spanKind : oteltrace .SpanKindClient .String (),
1708
+ events : []trace.Event {{Name : delayedResolutionEventName }},
1709
+ }
1710
+ spans , err := waitForTraceSpans (ctx , exporter , []traceSpanInfo {wantSpanInfo })
1711
+ if err != nil {
1712
+ t .Fatal (err )
1713
+ }
1714
+ verifyTrace (t , spans , wantSpanInfo )
1715
+ })
1716
+ }
1717
+ }
1718
+
1719
+ func verifyTrace (t * testing.T , spans tracetest.SpanStubs , want traceSpanInfo ) {
1720
+ match := false
1721
+ for _ , span := range spans {
1722
+ if span .Name == want .name && span .SpanKind .String () == want .spanKind {
1723
+ match = true
1724
+ if diff := cmp .Diff (want .events , span .Events , cmpopts .IgnoreFields (trace.Event {}, "Time" )); diff != "" {
1725
+ t .Errorf ("Span event mismatch for %q (kind: %s) (-want +got):\n %s" ,
1726
+ want .name , want .spanKind , diff )
1727
+ }
1728
+ break
1729
+ }
1730
+ }
1731
+ if ! match {
1732
+ t .Errorf ("Expected span not found: %q (kind: %s)" , want .name , want .spanKind )
1733
+ }
1734
+ }
0 commit comments