Skip to content

Commit 1feeaec

Browse files
authored
stats: Add optional locality label in cluster_impl picker (#7434)
1 parent 9671c4a commit 1feeaec

File tree

5 files changed

+55
-18
lines changed

5 files changed

+55
-18
lines changed

stats/opentelemetry/client_metrics.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,12 @@ func (h *clientStatsHandler) TagRPC(ctx context.Context, info *stats.RPCTagInfo)
150150
var labels *istats.Labels
151151
if labels = istats.GetLabels(ctx); labels == nil {
152152
labels = &istats.Labels{
153-
TelemetryLabels: make(map[string]string),
153+
// The defaults for all the per call labels from a plugin that
154+
// executes on the callpath that this OpenTelemetry component
155+
// currently supports.
156+
TelemetryLabels: map[string]string{
157+
"grpc.lb.locality": "",
158+
},
154159
}
155160
ctx = istats.SetLabels(ctx, labels)
156161
}
@@ -232,6 +237,8 @@ func (h *clientStatsHandler) processRPCEnd(ctx context.Context, ai *attemptInfo,
232237
}
233238

234239
for _, o := range h.options.MetricsOptions.OptionalLabels {
240+
// TODO: Add a filter for converting to unknown if not present in the
241+
// CSM Plugin Option layer by adding an optional labels API.
235242
if val, ok := ai.xdsLabels[o]; ok {
236243
attributes = append(attributes, otelattribute.String(o, val))
237244
}

stats/opentelemetry/csm/observability.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ func (o *perTargetDialOption) DialOptionForTarget(parsedTarget url.URL) grpc.Dia
7070

7171
func dialOptionWithCSMPluginOption(options opentelemetry.Options, po otelinternal.PluginOption) grpc.DialOption {
7272
options.MetricsOptions.OptionalLabels = []string{"csm.service_name", "csm.service_namespace_name"} // Attach the two xDS Optional Labels for this component to not filter out.
73+
return dialOptionSetCSM(options, po)
74+
}
75+
76+
func dialOptionSetCSM(options opentelemetry.Options, po otelinternal.PluginOption) grpc.DialOption {
7377
otelinternal.SetPluginOption.(func(options *opentelemetry.Options, po otelinternal.PluginOption))(&options, po)
7478
return opentelemetry.DialOption(options)
7579
}

stats/opentelemetry/csm/observability_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,12 @@ func (s) TestCSMPluginOptionStreaming(t *testing.T) {
425425
func unaryInterceptorAttachXDSLabels(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
426426
ctx = istats.SetLabels(ctx, &istats.Labels{
427427
TelemetryLabels: map[string]string{
428-
// mock what the cluster impl would write here ("csm." xDS Labels)
428+
// mock what the cluster impl would write here ("csm." xDS Labels
429+
// and locality label)
429430
"csm.service_name": "service_name_val",
430431
"csm.service_namespace_name": "service_namespace_val",
432+
433+
"grpc.lb.locality": "grpc.lb.locality_val",
431434
},
432435
})
433436

@@ -441,8 +444,9 @@ func unaryInterceptorAttachXDSLabels(ctx context.Context, method string, req, re
441444
// Optional Labels turned on. It then configures an interceptor to attach
442445
// labels, representing the cluster_impl picker. It then makes a unary RPC, and
443446
// expects xDS Labels labels to be attached to emitted relevant metrics. Full
444-
// xDS System alongside OpenTelemetry will be tested with interop. (there is
445-
// a test for xDS -> Stats handler and this tests -> OTel -> emission).
447+
// xDS System alongside OpenTelemetry will be tested with interop. (there is a
448+
// test for xDS -> Stats handler and this tests -> OTel -> emission). It also
449+
// tests the optional per call locality label in the same manner.
446450
func (s) TestXDSLabels(t *testing.T) {
447451
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
448452
defer cancel()
@@ -457,11 +461,11 @@ func (s) TestXDSLabels(t *testing.T) {
457461
}
458462

459463
po := newPluginOption(ctx)
460-
dopts := []grpc.DialOption{dialOptionWithCSMPluginOption(opentelemetry.Options{
464+
dopts := []grpc.DialOption{dialOptionSetCSM(opentelemetry.Options{
461465
MetricsOptions: opentelemetry.MetricsOptions{
462466
MeterProvider: provider,
463467
Metrics: opentelemetry.DefaultMetrics(),
464-
OptionalLabels: []string{"csm.service_name", "csm.service_namespace_name"},
468+
OptionalLabels: []string{"csm.service_name", "csm.service_namespace_name", "grpc.lb.locality"},
465469
},
466470
}, po), grpc.WithUnaryInterceptor(unaryInterceptorAttachXDSLabels)}
467471
if err := ss.Start(nil, dopts...); err != nil {
@@ -489,6 +493,7 @@ func (s) TestXDSLabels(t *testing.T) {
489493

490494
serviceNameAttr := attribute.String("csm.service_name", "service_name_val")
491495
serviceNamespaceAttr := attribute.String("csm.service_namespace_name", "service_namespace_val")
496+
localityAttr := attribute.String("grpc.lb.locality", "grpc.lb.locality_val")
492497
meshIDAttr := attribute.String("csm.mesh_id", "unknown")
493498
workloadCanonicalServiceAttr := attribute.String("csm.workload_canonical_service", "unknown")
494499
remoteWorkloadTypeAttr := attribute.String("csm.remote_workload_type", "unknown")
@@ -500,6 +505,7 @@ func (s) TestXDSLabels(t *testing.T) {
500505
unaryStatusAttr,
501506
serviceNameAttr,
502507
serviceNamespaceAttr,
508+
localityAttr,
503509
meshIDAttr,
504510
workloadCanonicalServiceAttr,
505511
remoteWorkloadTypeAttr,

test/xds/xds_telemetry_labels_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ import (
2828
"google.golang.org/grpc/internal/stubserver"
2929
"google.golang.org/grpc/internal/testutils"
3030
"google.golang.org/grpc/internal/testutils/xds/e2e"
31+
testgrpc "google.golang.org/grpc/interop/grpc_testing"
32+
testpb "google.golang.org/grpc/interop/grpc_testing"
3133
"google.golang.org/grpc/stats"
3234

3335
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
34-
testgrpc "google.golang.org/grpc/interop/grpc_testing"
35-
testpb "google.golang.org/grpc/interop/grpc_testing"
36+
"github.com/google/go-cmp/cmp"
3637
"google.golang.org/protobuf/types/known/structpb"
3738
)
3839

@@ -43,6 +44,9 @@ const serviceNamespaceKeyCSM = "csm.service_namespace_name"
4344
const serviceNameValue = "grpc-service"
4445
const serviceNamespaceValue = "grpc-service-namespace"
4546

47+
const localityKey = "grpc.lb.locality"
48+
const localityValue = `{"region":"region-1","zone":"zone-1","subZone":"subzone-1"}`
49+
4650
// TestTelemetryLabels tests that telemetry labels from CDS make their way to
4751
// the stats handler. The stats handler sets the mutable context value that the
4852
// cluster impl picker will write telemetry labels to, and then the stats
@@ -126,13 +130,14 @@ func (fsh *fakeStatsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
126130
// aren't started. All of these should have access to the desired telemetry
127131
// labels.
128132
case *stats.OutPayload, *stats.InPayload, *stats.End:
129-
if label, ok := fsh.labels.TelemetryLabels[serviceNameKeyCSM]; !ok || label != serviceNameValue {
130-
fsh.t.Fatalf("for telemetry label %v, want: %v, got: %v", serviceNameKeyCSM, serviceNameValue, label)
133+
want := map[string]string{
134+
serviceNameKeyCSM: serviceNameValue,
135+
serviceNamespaceKeyCSM: serviceNamespaceValue,
136+
localityKey: localityValue,
131137
}
132-
if label, ok := fsh.labels.TelemetryLabels[serviceNamespaceKeyCSM]; !ok || label != serviceNamespaceValue {
133-
fsh.t.Fatalf("for telemetry label %v, want: %v, got: %v", serviceNamespaceKeyCSM, serviceNamespaceValue, label)
138+
if diff := cmp.Diff(fsh.labels.TelemetryLabels, want); diff != "" {
139+
fsh.t.Fatalf("fsh.labels.TelemetryLabels (-got +want): %v", diff)
134140
}
135-
136141
default:
137142
// Nothing to assert for the other stats.Handler callouts.
138143
}

xds/internal/balancer/clusterimpl/picker.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package clusterimpl
2020

2121
import (
22+
"context"
23+
2224
v3orcapb "github.com/cncf/xds/go/xds/data/orca/v3"
2325
"google.golang.org/grpc/balancer"
2426
"google.golang.org/grpc/codes"
@@ -96,14 +98,23 @@ func (b *clusterImplBalancer) newPicker(config *dropConfigs) *picker {
9698
}
9799
}
98100

101+
func telemetryLabels(ctx context.Context) map[string]string {
102+
if ctx == nil {
103+
return nil
104+
}
105+
labels := stats.GetLabels(ctx)
106+
if labels == nil {
107+
return nil
108+
}
109+
return labels.TelemetryLabels
110+
}
111+
99112
func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
100113
// Unconditionally set labels if present, even dropped or queued RPC's can
101114
// use these labels.
102-
if info.Ctx != nil {
103-
if labels := stats.GetLabels(info.Ctx); labels != nil && labels.TelemetryLabels != nil {
104-
for key, value := range d.telemetryLabels {
105-
labels.TelemetryLabels[key] = value
106-
}
115+
if labels := telemetryLabels(info.Ctx); labels != nil {
116+
for key, value := range d.telemetryLabels {
117+
labels[key] = value
107118
}
108119
}
109120

@@ -156,6 +167,10 @@ func (d *picker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
156167
return pr, err
157168
}
158169

170+
if labels := telemetryLabels(info.Ctx); labels != nil {
171+
labels["grpc.lb.locality"] = lIDStr
172+
}
173+
159174
if d.loadStore != nil {
160175
d.loadStore.CallStarted(lIDStr)
161176
oldDone := pr.Done

0 commit comments

Comments
 (0)