-
Notifications
You must be signed in to change notification settings - Fork 536
feat: gcp pubsub admin endpoints #4957
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ojproductions
wants to merge
60
commits into
main
Choose a base branch
from
onzia/gcp-pubsub-admin-endpoints
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
60 commits
Select commit
Hold shift + click to select a range
cbae177
debug logs
ojproductions f661d04
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia-gcp-…
ojproductions 33560e9
push subscriptions w hardcoded values
ojproductions eb5b0f5
update push subscriptions tags
ojproductions b9109b4
Merge branch 'main' into onzia-gcp-pubsub
ojproductions 0b936fa
remove pubsub as proxy
ojproductions 7c8025c
update var naming
ojproductions d9582ae
comments
ojproductions 72bd395
tests
ojproductions fcc99e7
Merge branch 'main' into onzia-gcp-pubsub
ojproductions 23c9ac8
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia-gcp-…
ojproductions c2b2ea6
add span link option
ojproductions 3083cf0
add span links for push subscriptions
ojproductions be5bcb7
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia-gcp-…
ojproductions 5ba94cb
Merge branch 'main' into onzia/gcp-pubsub-span-links
ojproductions ba0859b
pr feedback
ojproductions 188c769
add pubsub header name validation
ojproductions 6ca2c21
Merge branch 'onzia-gcp-pubsub' into onzia/gcp-pubsub-span-links
ojproductions 44ac739
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia-gcp-…
ojproductions 7c77890
Pr changes
ojproductions 8365cfb
add log details
ojproductions 083c414
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia/gcp-…
ojproductions 9f655ad
instrument admin endpoints
ojproductions eb814b2
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia-gcp-…
ojproductions 7e35f93
use net/http component
ojproductions 8ac068c
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia/gcp-…
ojproductions 225ba5a
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia/gcp-…
ojproductions 7fed46f
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia/gcp-…
ojproductions cc1c3fd
comments
ojproductions ae5ddaf
implement v1
ojproductions 71fbbf9
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia/gcp-…
ojproductions d1f5adb
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia/gcp-…
ojproductions 323508c
Merge branch 'main' of github.com:DataDog/dd-trace-go into onzia-gcp-…
ojproductions 32f5689
Merge branch 'onzia-gcp-pubsub' into onzia/gcp-pubsub-span-links
ojproductions ca2824f
v2 span links tests
ojproductions 3c0147b
Merge branch 'onzia/gcp-pubsub-span-links' into onzia/gcp-pubsub-admi…
ojproductions f4adada
update component in tests
ojproductions 26a1743
Merge branch 'onzia-gcp-pubsub' into onzia/gcp-pubsub-span-links
ojproductions 726ae46
Merge branch 'onzia/gcp-pubsub-span-links' into onzia/gcp-pubsub-admi…
ojproductions 712b634
use new config implementation
ojproductions fb1c555
Merge branch 'onzia/gcp-pubsub-span-links' into onzia/gcp-pubsub-admi…
ojproductions d84e42b
Merge branch 'main' into onzia/gcp-pubsub-admin-endpoints
ojproductions 73cd7b6
implement admin tracing via grpc interceptors instead of wrapping
ojproductions 907136f
copyrght header
ojproductions ef1d999
add back v2.11
ojproductions 2a5e4ca
handle empty resource case
ojproductions e81adfc
comments
ojproductions 6d9de88
combine orchestrion admin endpoints test into pubsub package
ojproductions bc69a39
Merge branch 'main' into onzia/gcp-pubsub-admin-endpoints
ojproductions ca33ec8
undo go version change
ojproductions 2a3c953
update pubsub version
ojproductions e1f9963
update go mod
ojproductions f688f77
go mod stuff
ojproductions d844b12
ci fix maybe
ojproductions 2ce9ef1
apply copilot nits
ojproductions 9b7bdf1
Merge branch 'main' into onzia/gcp-pubsub-admin-endpoints
ojproductions afabbfa
pass ci
ojproductions 07f7fe7
Merge branch 'main' into onzia/gcp-pubsub-admin-endpoints
ojproductions 0d80409
Merge branch 'main' into onzia/gcp-pubsub-admin-endpoints
ojproductions 1d0ae00
Merge branch 'main' into onzia/gcp-pubsub-admin-endpoints
ojproductions File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,302 @@ | ||
| // Unless explicitly stated otherwise all files in this repository are licensed | ||
| // under the Apache License Version 2.0. | ||
| // This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| // Copyright 2025 Datadog, Inc. | ||
|
|
||
| package pubsub | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "time" | ||
|
|
||
| vkit "cloud.google.com/go/pubsub/apiv1" | ||
| pubsubpb "cloud.google.com/go/pubsub/apiv1/pubsubpb" | ||
| "cloud.google.com/go/pubsub/pstest" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| "google.golang.org/api/iterator" | ||
| "google.golang.org/api/option" | ||
| "google.golang.org/grpc" | ||
| "google.golang.org/grpc/credentials/insecure" | ||
|
|
||
| "github.com/DataDog/dd-trace-go/v2/contrib/cloud.google.com/go/pubsubtrace" | ||
| "github.com/DataDog/dd-trace-go/v2/ddtrace/ext" | ||
| "github.com/DataDog/dd-trace-go/v2/ddtrace/mocktracer" | ||
| ) | ||
|
|
||
| const adminProjectID = "project" | ||
|
|
||
| func setupAdmin(t *testing.T) (context.Context, mocktracer.Tracer, *vkit.PublisherClient, *vkit.SubscriberClient, *vkit.SchemaClient) { | ||
| mt := mocktracer.Start() | ||
| t.Cleanup(mt.Stop) | ||
|
|
||
| srv := pstest.NewServer() | ||
| t.Cleanup(func() { assert.NoError(t, srv.Close()) }) | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
| t.Cleanup(cancel) | ||
|
|
||
| // The admin GAPIC clients issue their RPCs over this connection, so | ||
| // installing the interceptor here traces their admin operations. | ||
| conn, err := grpc.NewClient(srv.Addr, | ||
| grpc.WithTransportCredentials(insecure.NewCredentials()), | ||
| grpc.WithChainUnaryInterceptor(pubsubtrace.UnaryAdminInterceptorV1()), | ||
| ) | ||
| require.NoError(t, err) | ||
| t.Cleanup(func() { assert.NoError(t, conn.Close()) }) | ||
|
|
||
| pc, err := vkit.NewPublisherClient(ctx, option.WithGRPCConn(conn)) | ||
| require.NoError(t, err) | ||
| sc, err := vkit.NewSubscriberClient(ctx, option.WithGRPCConn(conn)) | ||
| require.NoError(t, err) | ||
| schema, err := vkit.NewSchemaClient(ctx, option.WithGRPCConn(conn)) | ||
| require.NoError(t, err) | ||
|
|
||
| return ctx, mt, pc, sc, schema | ||
| } | ||
|
|
||
| func topicName(id string) string { | ||
| return fmt.Sprintf("projects/%s/topics/%s", adminProjectID, id) | ||
| } | ||
|
|
||
| func subName(id string) string { | ||
| return fmt.Sprintf("projects/%s/subscriptions/%s", adminProjectID, id) | ||
| } | ||
|
|
||
| func snapshotName(id string) string { | ||
| return fmt.Sprintf("projects/%s/snapshots/%s", adminProjectID, id) | ||
| } | ||
|
|
||
| func schemaName(id string) string { | ||
| return fmt.Sprintf("projects/%s/schemas/%s", adminProjectID, id) | ||
| } | ||
|
|
||
| func projectName() string { | ||
| return fmt.Sprintf("projects/%s", adminProjectID) | ||
| } | ||
|
|
||
| func drain[T any](t *testing.T, next func() (T, error)) { | ||
| t.Helper() | ||
| for { | ||
| if _, err := next(); err == iterator.Done { | ||
| return | ||
| } else { | ||
| require.NoError(t, err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestTraceAdminTopicOperations(t *testing.T) { | ||
| ctx, mt, pc, _, _ := setupAdmin(t) | ||
|
|
||
| _, err := pc.CreateTopic(ctx, &pubsubpb.Topic{Name: topicName("topic")}) | ||
| require.NoError(t, err) | ||
|
|
||
| _, err = pc.GetTopic(ctx, &pubsubpb.GetTopicRequest{Topic: topicName("topic")}) | ||
| require.NoError(t, err) | ||
|
|
||
| it := pc.ListTopics(ctx, &pubsubpb.ListTopicsRequest{Project: projectName()}) | ||
| drain(t, it.Next) | ||
|
|
||
| err = pc.DeleteTopic(ctx, &pubsubpb.DeleteTopicRequest{Topic: topicName("topic")}) | ||
| require.NoError(t, err) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 4) | ||
|
|
||
| assertAdminSpan(t, spans[0], "CreateTopic", "CreateTopic "+topicName("topic")) | ||
| assertAdminSpan(t, spans[1], "GetTopic", "GetTopic "+topicName("topic")) | ||
| assertAdminSpan(t, spans[2], "ListTopics", "ListTopics "+projectName()) | ||
| assertAdminSpan(t, spans[3], "DeleteTopic", "DeleteTopic "+topicName("topic")) | ||
| } | ||
|
|
||
| func TestTraceAdminSubscriptionOperations(t *testing.T) { | ||
| ctx, mt, pc, sc, _ := setupAdmin(t) | ||
|
|
||
| _, err := pc.CreateTopic(ctx, &pubsubpb.Topic{Name: topicName("topic")}) | ||
| require.NoError(t, err) | ||
|
|
||
| _, err = sc.CreateSubscription(ctx, &pubsubpb.Subscription{ | ||
| Name: subName("sub"), | ||
| Topic: topicName("topic"), | ||
| }) | ||
| require.NoError(t, err) | ||
|
|
||
| _, err = sc.GetSubscription(ctx, &pubsubpb.GetSubscriptionRequest{Subscription: subName("sub")}) | ||
| require.NoError(t, err) | ||
|
|
||
| it := sc.ListSubscriptions(ctx, &pubsubpb.ListSubscriptionsRequest{Project: projectName()}) | ||
| drain(t, it.Next) | ||
|
|
||
| err = sc.DeleteSubscription(ctx, &pubsubpb.DeleteSubscriptionRequest{Subscription: subName("sub")}) | ||
| require.NoError(t, err) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 5) | ||
|
|
||
| assertAdminSpan(t, spans[0], "CreateTopic", "CreateTopic "+topicName("topic")) | ||
| assertAdminSpan(t, spans[1], "CreateSubscription", "CreateSubscription "+subName("sub")) | ||
| assertAdminSpan(t, spans[2], "GetSubscription", "GetSubscription "+subName("sub")) | ||
| assertAdminSpan(t, spans[3], "ListSubscriptions", "ListSubscriptions "+projectName()) | ||
| assertAdminSpan(t, spans[4], "DeleteSubscription", "DeleteSubscription "+subName("sub")) | ||
| } | ||
|
|
||
| func TestTraceAdminSnapshotOperations(t *testing.T) { | ||
| ctx, mt, pc, sc, _ := setupAdmin(t) | ||
|
|
||
| _, err := pc.CreateTopic(ctx, &pubsubpb.Topic{Name: topicName("topic")}) | ||
| require.NoError(t, err) | ||
| _, err = sc.CreateSubscription(ctx, &pubsubpb.Subscription{ | ||
| Name: subName("sub"), | ||
| Topic: topicName("topic"), | ||
| }) | ||
| require.NoError(t, err) | ||
|
|
||
| // pstest does not implement snapshots, so these RPCs error — but the | ||
| // interceptor must still emit spans with the resolved resource path. | ||
| _, err = sc.CreateSnapshot(ctx, &pubsubpb.CreateSnapshotRequest{ | ||
| Name: snapshotName("snap"), | ||
| Subscription: subName("sub"), | ||
| }) | ||
| require.Error(t, err) | ||
|
|
||
| _, err = sc.GetSnapshot(ctx, &pubsubpb.GetSnapshotRequest{Snapshot: snapshotName("snap")}) | ||
| require.Error(t, err) | ||
|
|
||
| it := sc.ListSnapshots(ctx, &pubsubpb.ListSnapshotsRequest{Project: projectName()}) | ||
| _, err = it.Next() | ||
| require.Error(t, err) | ||
| require.NotEqual(t, iterator.Done, err) | ||
|
|
||
| err = sc.DeleteSnapshot(ctx, &pubsubpb.DeleteSnapshotRequest{Snapshot: snapshotName("snap")}) | ||
| require.Error(t, err) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 6) | ||
|
|
||
| assertAdminSpan(t, spans[0], "CreateTopic", "CreateTopic "+topicName("topic")) | ||
| assertAdminSpan(t, spans[1], "CreateSubscription", "CreateSubscription "+subName("sub")) | ||
| assertAdminSpan(t, spans[2], "CreateSnapshot", "CreateSnapshot "+snapshotName("snap")) | ||
| assert.NotNil(t, spans[2].Tag(ext.ErrorMsg)) | ||
| assertAdminSpan(t, spans[3], "GetSnapshot", "GetSnapshot "+snapshotName("snap")) | ||
| assert.NotNil(t, spans[3].Tag(ext.ErrorMsg)) | ||
| assertAdminSpan(t, spans[4], "ListSnapshots", "ListSnapshots "+projectName()) | ||
| assert.NotNil(t, spans[4].Tag(ext.ErrorMsg)) | ||
| assertAdminSpan(t, spans[5], "DeleteSnapshot", "DeleteSnapshot "+snapshotName("snap")) | ||
| assert.NotNil(t, spans[5].Tag(ext.ErrorMsg)) | ||
| } | ||
|
|
||
| func TestTraceAdminSchemaOperations(t *testing.T) { | ||
| ctx, mt, _, _, sc := setupAdmin(t) | ||
|
|
||
| const avroDef = `{"type":"record","name":"Test","fields":[{"name":"f","type":"string"}]}` | ||
| _, err := sc.CreateSchema(ctx, &pubsubpb.CreateSchemaRequest{ | ||
| Parent: projectName(), | ||
| Schema: &pubsubpb.Schema{ | ||
| Type: pubsubpb.Schema_AVRO, | ||
| Definition: avroDef, | ||
| }, | ||
| SchemaId: "schema", | ||
| }) | ||
| require.NoError(t, err) | ||
|
|
||
| _, err = sc.GetSchema(ctx, &pubsubpb.GetSchemaRequest{Name: schemaName("schema")}) | ||
| require.NoError(t, err) | ||
|
|
||
| it := sc.ListSchemas(ctx, &pubsubpb.ListSchemasRequest{Parent: projectName()}) | ||
| drain(t, it.Next) | ||
|
|
||
| err = sc.DeleteSchema(ctx, &pubsubpb.DeleteSchemaRequest{Name: schemaName("schema")}) | ||
| require.NoError(t, err) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 4) | ||
|
|
||
| assertAdminSpan(t, spans[0], "CreateSchema", "CreateSchema "+projectName()) | ||
| assertAdminSpan(t, spans[1], "GetSchema", "GetSchema "+schemaName("schema")) | ||
| assertAdminSpan(t, spans[2], "ListSchemas", "ListSchemas "+projectName()) | ||
| assertAdminSpan(t, spans[3], "DeleteSchema", "DeleteSchema "+schemaName("schema")) | ||
| } | ||
|
|
||
| func TestTraceAdminError(t *testing.T) { | ||
| ctx, mt, pc, _, _ := setupAdmin(t) | ||
|
|
||
| _, err := pc.GetTopic(ctx, &pubsubpb.GetTopicRequest{Topic: topicName("missing")}) | ||
| require.Error(t, err) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 1) | ||
| assert.Equal(t, err.Error(), spans[0].Tag(ext.ErrorMsg)) | ||
| assertAdminSpan(t, spans[0], "GetTopic", "GetTopic "+topicName("missing")) | ||
| } | ||
|
|
||
| func TestTraceAdminMissingResource(t *testing.T) { | ||
| ctx, mt, pc, _, _ := setupAdmin(t) | ||
|
|
||
| // Recognized admin RPCs with an empty resource field must still emit a | ||
| // span; TraceAdmin falls back to a method-only resource name. | ||
| _, createErr := pc.CreateTopic(ctx, &pubsubpb.Topic{}) | ||
| _, getErr := pc.GetTopic(ctx, &pubsubpb.GetTopicRequest{}) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 2) | ||
|
|
||
| assert.Equal(t, "gcp.pubsub.request", spans[0].OperationName()) | ||
| assert.Equal(t, "CreateTopic", spans[0].Tag(ext.ResourceName)) | ||
| assert.Equal(t, "CreateTopic", spans[0].Tag("pubsub.method")) | ||
| assert.Nil(t, spans[0].Tag(ext.GCPProjectID)) | ||
| if createErr != nil { | ||
| assert.Equal(t, createErr.Error(), spans[0].Tag(ext.ErrorMsg)) | ||
| } | ||
|
|
||
| assert.Equal(t, "gcp.pubsub.request", spans[1].OperationName()) | ||
| assert.Equal(t, "GetTopic", spans[1].Tag(ext.ResourceName)) | ||
| assert.Equal(t, "GetTopic", spans[1].Tag("pubsub.method")) | ||
| assert.Nil(t, spans[1].Tag(ext.GCPProjectID)) | ||
| if getErr != nil { | ||
| assert.Equal(t, getErr.Error(), spans[1].Tag(ext.ErrorMsg)) | ||
| } | ||
| } | ||
|
|
||
| func TestTraceAdminWithService(t *testing.T) { | ||
| mt := mocktracer.Start() | ||
| defer mt.Stop() | ||
|
|
||
| srv := pstest.NewServer() | ||
| defer func() { assert.NoError(t, srv.Close()) }() | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | ||
| defer cancel() | ||
|
|
||
| conn, err := grpc.NewClient(srv.Addr, | ||
| grpc.WithTransportCredentials(insecure.NewCredentials()), | ||
| grpc.WithChainUnaryInterceptor(pubsubtrace.UnaryAdminInterceptorV1(WithService("my-admin-service"))), | ||
| ) | ||
| require.NoError(t, err) | ||
| defer func() { assert.NoError(t, conn.Close()) }() | ||
|
|
||
| pc, err := vkit.NewPublisherClient(ctx, option.WithGRPCConn(conn)) | ||
| require.NoError(t, err) | ||
|
|
||
| _, err = pc.CreateTopic(ctx, &pubsubpb.Topic{Name: topicName("topic")}) | ||
| require.NoError(t, err) | ||
|
|
||
| spans := mt.FinishedSpans() | ||
| require.Len(t, spans, 1) | ||
| assert.Equal(t, "my-admin-service", spans[0].Tag(ext.ServiceName)) | ||
| } | ||
|
|
||
| func assertAdminSpan(t *testing.T, span *mocktracer.Span, method, resource string) { | ||
| t.Helper() | ||
| assert.Equal(t, "gcp.pubsub.request", span.OperationName()) | ||
| assert.Equal(t, resource, span.Tag(ext.ResourceName)) | ||
| assert.Equal(t, ext.SpanTypeWorker, span.Tag(ext.SpanType)) | ||
| assert.Equal(t, ext.SpanKindClient, span.Tag(ext.SpanKind)) | ||
| assert.Equal(t, "cloud.google.com/go/pubsub.v1", span.Tag(ext.Component)) | ||
| assert.Equal(t, ext.MessagingSystemGCPPubsub, span.Tag(ext.MessagingSystem)) | ||
| assert.Equal(t, method, span.Tag("pubsub.method")) | ||
| assert.Equal(t, adminProjectID, span.Tag(ext.GCPProjectID)) | ||
| assert.Equal(t, "cloud.google.com/go/pubsub.v1", span.Integration()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.