Skip to content

Commit f8291fb

Browse files
authored
feat: trace watchable messages (#7403)
* feat: trace watchable messages Signed-off-by: Shreemaan Abhishek <[email protected]>
1 parent 174a3aa commit f8291fb

File tree

21 files changed

+753
-107
lines changed

21 files changed

+753
-107
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ require (
5959
go.opentelemetry.io/otel/metric v1.38.0
6060
go.opentelemetry.io/otel/sdk v1.38.0
6161
go.opentelemetry.io/otel/sdk/metric v1.38.0
62+
go.opentelemetry.io/otel/trace v1.38.0
6263
go.opentelemetry.io/proto/otlp v1.9.0
6364
go.uber.org/zap v1.27.1
6465
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
@@ -290,7 +291,6 @@ require (
290291
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
291292
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
292293
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
293-
go.opentelemetry.io/otel/trace v1.38.0 // indirect
294294
go.uber.org/multierr v1.11.0 // indirect
295295
go.yaml.in/yaml/v2 v2.4.3 // indirect
296296
go.yaml.in/yaml/v3 v3.0.4 // indirect

internal/admin/console/api.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,14 +260,14 @@ func (h *Handler) loadConfigDump() ConfigDumpInfo {
260260

261261
if h.providerResources != nil {
262262
// Load controller resources directly from the provider resources
263-
controllerResources := h.providerResources.GatewayAPIResources.LoadAll()
263+
controllerResourcesContext := h.providerResources.GatewayAPIResources.LoadAll()
264264

265-
for _, resources := range controllerResources {
266-
if resources == nil {
265+
for _, resourcesContext := range controllerResourcesContext {
266+
if resourcesContext == nil {
267267
continue
268268
}
269269

270-
for _, res := range *resources {
270+
for _, res := range *resourcesContext.Resources {
271271
if res == nil {
272272
continue
273273
}

internal/admin/console/api_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func TestLoadConfigDumpWithData(t *testing.T) {
151151
// This test focuses on the basic functionality
152152
providerRes := &message.ProviderResources{}
153153
// Initialize empty watchable map
154-
providerRes.GatewayAPIResources = watchable.Map[string, *resource.ControllerResources]{}
154+
providerRes.GatewayAPIResources = watchable.Map[string, *resource.ControllerResourcesContext]{}
155155

156156
// Skip storing to avoid watchable copy issues
157157
// providerResources.Store("test", providerRes)

internal/cmd/server.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/envoyproxy/gateway/internal/message"
2525
"github.com/envoyproxy/gateway/internal/metrics"
2626
providerrunner "github.com/envoyproxy/gateway/internal/provider/runner"
27+
"github.com/envoyproxy/gateway/internal/traces"
2728
xdsrunner "github.com/envoyproxy/gateway/internal/xds/runner"
2829
)
2930

@@ -171,6 +172,10 @@ func startRunners(ctx context.Context, cfg *config.Server, runnerErrors *message
171172
runners := []struct {
172173
runner Runner
173174
}{
175+
{
176+
// Start the Traces Server
177+
runner: traces.New(cfg),
178+
},
174179
{
175180
// Start the Provider Service
176181
// It fetches the resources from the configured provider type

internal/gatewayapi/resource/resource.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
package resource
77

88
import (
9+
"context"
10+
"reflect"
911
"sort"
1012

1113
certificatesv1b1 "k8s.io/api/certificates/v1beta1"
@@ -141,6 +143,47 @@ func (r *Resources) GetEndpointSlicesForBackend(svcNamespace, svcName, backendKi
141143
// ControllerResources holds all the GatewayAPI resources per GatewayClass
142144
type ControllerResources []*Resources
143145

146+
// ControllerResourcesContext wraps ControllerResources with trace context
147+
// for propagating spans across async message boundaries
148+
type ControllerResourcesContext struct {
149+
Resources *ControllerResources
150+
Context context.Context
151+
}
152+
153+
// DeepCopy creates a new ControllerResourcesContext.
154+
// The Context field is preserved (not deep copied) since contexts are meant to be passed around.
155+
func (c *ControllerResourcesContext) DeepCopy() *ControllerResourcesContext {
156+
if c == nil {
157+
return nil
158+
}
159+
var resourcesCopy *ControllerResources
160+
if c.Resources != nil {
161+
resourcesCopy = c.Resources.DeepCopy()
162+
}
163+
return &ControllerResourcesContext{
164+
Resources: resourcesCopy,
165+
Context: c.Context,
166+
}
167+
}
168+
169+
// Equal compares two Resources objects for equality.
170+
func (c *ControllerResourcesContext) Equal(other *ControllerResourcesContext) bool {
171+
if c == nil && other == nil {
172+
return true
173+
}
174+
if c == nil || other == nil {
175+
return false
176+
}
177+
if c.Resources == nil && other.Resources == nil {
178+
return true
179+
}
180+
if c.Resources == nil || other.Resources == nil {
181+
return false
182+
}
183+
184+
return reflect.DeepEqual(c.Resources, other.Resources)
185+
}
186+
144187
// DeepCopy creates a new ControllerResources.
145188
// It is handwritten since the tooling was unable to copy into a new slice
146189
func (c *ControllerResources) DeepCopy() *ControllerResources {

internal/gatewayapi/resource/resource_test.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package resource
77

88
import (
9+
"context"
910
"testing"
1011

1112
"github.com/google/go-cmp/cmp"
@@ -130,6 +131,38 @@ func TestEqualXds(t *testing.T) {
130131
}
131132
}
132133

134+
func TestEqualControllerResourcesContext(t *testing.T) {
135+
c1 := context.Background()
136+
c2 := context.TODO()
137+
r1 := &ControllerResourcesContext{
138+
Resources: &ControllerResources{
139+
{
140+
GatewayClass: &gwapiv1.GatewayClass{
141+
ObjectMeta: metav1.ObjectMeta{
142+
Name: "foo",
143+
},
144+
},
145+
},
146+
},
147+
Context: c1,
148+
}
149+
r2 := &ControllerResourcesContext{
150+
Resources: &ControllerResources{
151+
{
152+
GatewayClass: &gwapiv1.GatewayClass{
153+
ObjectMeta: metav1.ObjectMeta{
154+
Name: "foo",
155+
},
156+
},
157+
},
158+
},
159+
Context: c2,
160+
}
161+
162+
assert.True(t, r1.Equal(r2))
163+
assert.True(t, r2.Equal(r1))
164+
}
165+
133166
func TestGetEndpointSlicesForBackendDualStack(t *testing.T) {
134167
// Test data setup
135168
dualStackService := &discoveryv1.EndpointSlice{
@@ -205,3 +238,141 @@ func TestGetEndpointSlicesForBackendDualStack(t *testing.T) {
205238
}
206239
})
207240
}
241+
242+
func TestControllerResourcesContextDeepCopy(t *testing.T) {
243+
tests := []struct {
244+
name string
245+
ctx *ControllerResourcesContext
246+
}{
247+
{
248+
name: "nil context",
249+
ctx: nil,
250+
},
251+
{
252+
name: "empty context",
253+
ctx: &ControllerResourcesContext{
254+
Resources: &ControllerResources{},
255+
Context: context.Background(),
256+
},
257+
},
258+
{
259+
name: "context with resources",
260+
ctx: &ControllerResourcesContext{
261+
Resources: &ControllerResources{
262+
{
263+
GatewayClass: &gwapiv1.GatewayClass{
264+
ObjectMeta: metav1.ObjectMeta{
265+
Name: "test-gateway-class",
266+
},
267+
},
268+
},
269+
},
270+
Context: context.Background(),
271+
},
272+
},
273+
}
274+
275+
for _, tc := range tests {
276+
t.Run(tc.name, func(t *testing.T) {
277+
copied := tc.ctx.DeepCopy()
278+
279+
if tc.ctx == nil {
280+
assert.Nil(t, copied)
281+
return
282+
}
283+
284+
// Verify the copy is not nil
285+
require.NotNil(t, copied)
286+
287+
// Verify the copy is a different object
288+
assert.NotSame(t, tc.ctx, copied)
289+
290+
// Verify Resources are deep copied
291+
if tc.ctx.Resources != nil {
292+
require.NotNil(t, copied.Resources)
293+
assert.NotSame(t, tc.ctx.Resources, copied.Resources)
294+
295+
// Verify the contents are equal
296+
assert.Len(t, *copied.Resources, len(*tc.ctx.Resources))
297+
}
298+
299+
// Verify Context is preserved (not deep copied, same reference)
300+
assert.Equal(t, tc.ctx.Context, copied.Context)
301+
})
302+
}
303+
}
304+
305+
func TestControllerResourcesDeepCopy(t *testing.T) {
306+
tests := []struct {
307+
name string
308+
resources *ControllerResources
309+
}{
310+
{
311+
name: "nil resources",
312+
resources: nil,
313+
},
314+
{
315+
name: "empty resources",
316+
resources: &ControllerResources{},
317+
},
318+
{
319+
name: "resources with gateway class",
320+
resources: &ControllerResources{
321+
{
322+
GatewayClass: &gwapiv1.GatewayClass{
323+
ObjectMeta: metav1.ObjectMeta{
324+
Name: "test-gateway-class",
325+
},
326+
},
327+
},
328+
},
329+
},
330+
{
331+
name: "multiple resources",
332+
resources: &ControllerResources{
333+
{
334+
GatewayClass: &gwapiv1.GatewayClass{
335+
ObjectMeta: metav1.ObjectMeta{
336+
Name: "gateway-class-1",
337+
},
338+
},
339+
},
340+
{
341+
GatewayClass: &gwapiv1.GatewayClass{
342+
ObjectMeta: metav1.ObjectMeta{
343+
Name: "gateway-class-2",
344+
},
345+
},
346+
},
347+
},
348+
},
349+
}
350+
351+
for _, tc := range tests {
352+
t.Run(tc.name, func(t *testing.T) {
353+
copied := tc.resources.DeepCopy()
354+
355+
if tc.resources == nil {
356+
assert.Nil(t, copied)
357+
return
358+
}
359+
360+
// Verify the copy is not nil
361+
require.NotNil(t, copied)
362+
363+
// Verify the copy is a different object
364+
assert.NotSame(t, tc.resources, copied)
365+
366+
// Verify the length is the same
367+
assert.Len(t, *copied, len(*tc.resources))
368+
369+
// Verify each resource is deep copied
370+
for i := range *tc.resources {
371+
if (*tc.resources)[i] != nil {
372+
require.NotNil(t, (*copied)[i])
373+
assert.NotSame(t, (*tc.resources)[i], (*copied)[i])
374+
}
375+
}
376+
})
377+
}
378+
}

0 commit comments

Comments
 (0)