google.golang.org/grpc@v1.72.2/test/xds/xds_telemetry_labels_test.go (about) 1 /* 2 * 3 * Copyright 2024 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package xds_test 19 20 import ( 21 "context" 22 "fmt" 23 "testing" 24 25 "google.golang.org/grpc" 26 "google.golang.org/grpc/credentials/insecure" 27 istats "google.golang.org/grpc/internal/stats" 28 "google.golang.org/grpc/internal/stubserver" 29 "google.golang.org/grpc/internal/testutils" 30 "google.golang.org/grpc/internal/testutils/xds/e2e" 31 "google.golang.org/grpc/internal/testutils/xds/e2e/setup" 32 testgrpc "google.golang.org/grpc/interop/grpc_testing" 33 testpb "google.golang.org/grpc/interop/grpc_testing" 34 "google.golang.org/grpc/stats" 35 36 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 37 "github.com/google/go-cmp/cmp" 38 "google.golang.org/protobuf/types/known/structpb" 39 ) 40 41 const serviceNameKey = "service_name" 42 const serviceNameKeyCSM = "csm.service_name" 43 const serviceNamespaceKey = "service_namespace" 44 const serviceNamespaceKeyCSM = "csm.service_namespace_name" 45 const serviceNameValue = "grpc-service" 46 const serviceNamespaceValue = "grpc-service-namespace" 47 48 const localityKey = "grpc.lb.locality" 49 const localityValue = `{"region":"region-1","zone":"zone-1","subZone":"subzone-1"}` 50 51 // TestTelemetryLabels tests that telemetry labels from CDS make their way to 52 // the stats handler. The stats handler sets the mutable context value that the 53 // cluster impl picker will write telemetry labels to, and then the stats 54 // handler asserts that subsequent HandleRPC calls from the RPC lifecycle 55 // contain telemetry labels that it can see. 56 func (s) TestTelemetryLabels(t *testing.T) { 57 managementServer, nodeID, _, xdsResolver := setup.ManagementServerAndResolver(t) 58 59 server := stubserver.StartTestService(t, nil) 60 defer server.Stop() 61 62 const xdsServiceName = "my-service-client-side-xds" 63 resources := e2e.DefaultClientResources(e2e.ResourceParams{ 64 DialTarget: xdsServiceName, 65 NodeID: nodeID, 66 Host: "localhost", 67 Port: testutils.ParsePort(t, server.Address), 68 SecLevel: e2e.SecurityLevelNone, 69 }) 70 71 resources.Clusters[0].Metadata = &v3corepb.Metadata{ 72 FilterMetadata: map[string]*structpb.Struct{ 73 "com.google.csm.telemetry_labels": { 74 Fields: map[string]*structpb.Value{ 75 serviceNameKey: structpb.NewStringValue(serviceNameValue), 76 serviceNamespaceKey: structpb.NewStringValue(serviceNamespaceValue), 77 }, 78 }, 79 }, 80 } 81 82 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 83 defer cancel() 84 if err := managementServer.Update(ctx, resources); err != nil { 85 t.Fatal(err) 86 } 87 88 fsh := &fakeStatsHandler{ 89 t: t, 90 } 91 92 cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", xdsServiceName), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(xdsResolver), grpc.WithStatsHandler(fsh)) 93 if err != nil { 94 t.Fatalf("failed to create a new client to local test server: %v", err) 95 } 96 defer cc.Close() 97 98 client := testgrpc.NewTestServiceClient(cc) 99 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 100 t.Fatalf("rpc EmptyCall() failed: %v", err) 101 } 102 } 103 104 type fakeStatsHandler struct { 105 labels *istats.Labels 106 107 t *testing.T 108 } 109 110 func (fsh *fakeStatsHandler) TagConn(ctx context.Context, _ *stats.ConnTagInfo) context.Context { 111 return ctx 112 } 113 114 func (fsh *fakeStatsHandler) HandleConn(context.Context, stats.ConnStats) {} 115 116 func (fsh *fakeStatsHandler) TagRPC(ctx context.Context, _ *stats.RPCTagInfo) context.Context { 117 labels := &istats.Labels{ 118 TelemetryLabels: make(map[string]string), 119 } 120 fsh.labels = labels 121 ctx = istats.SetLabels(ctx, labels) // ctx passed is immutable, however cluster_impl writes to the map of Telemetry Labels on the heap. 122 return ctx 123 } 124 125 func (fsh *fakeStatsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) { 126 switch rs.(type) { 127 // stats.Begin won't get Telemetry Labels because happens after picker 128 // picks. 129 130 // These three stats callouts trigger all metrics for OpenTelemetry that 131 // aren't started. All of these should have access to the desired telemetry 132 // labels. 133 case *stats.OutPayload, *stats.InPayload, *stats.End: 134 want := map[string]string{ 135 serviceNameKeyCSM: serviceNameValue, 136 serviceNamespaceKeyCSM: serviceNamespaceValue, 137 localityKey: localityValue, 138 } 139 if diff := cmp.Diff(fsh.labels.TelemetryLabels, want); diff != "" { 140 fsh.t.Fatalf("fsh.labels.TelemetryLabels (-got +want): %v", diff) 141 } 142 default: 143 // Nothing to assert for the other stats.Handler callouts. 144 } 145 146 }