grpc baggage

Signed-off-by: Cassandra Coyle <cassie@diagrid.io>
This commit is contained in:
Cassandra Coyle 2025-04-04 12:32:36 -05:00
parent d6a64d8946
commit b0e27ea2d9
No known key found for this signature in database
13 changed files with 336 additions and 56 deletions

View File

@ -18,6 +18,7 @@ import (
"github.com/dapr/dapr/pkg/api/grpc/proxy/codec"
"github.com/dapr/dapr/pkg/diagnostics"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
"github.com/dapr/dapr/pkg/resiliency"
"github.com/dapr/kit/utils"
)
@ -96,7 +97,7 @@ func (s *handler) handler(srv any, serverStream grpc.ServerStream) error {
// Fetch the AppId so we can reference it for resiliency.
ctx := serverStream.Context()
md, _ := metadata.FromIncomingContext(ctx)
v := md[diagnostics.GRPCProxyAppIDKey]
v := md[diagConsts.GRPCProxyAppIDKey]
// The app id check is handled in the StreamDirector. If we don't have it here, we just use a NoOp policy since we know the request is impossible.
var policyDef *resiliency.PolicyDefinition

View File

@ -61,6 +61,25 @@ const (
// Keys used in the context's metadata for streaming calls
// Note: these keys must always be all-lowercase
DaprCallLocalStreamMethodKey = "__dapr_calllocalstream_method"
// We have leveraged the code from opencensus-go plugin to adhere the w3c trace context.
// Reference : https://github.com/census-instrumentation/opencensus-go/blob/master/plugin/ochttp/propagation/tracecontext/propagation.go
// Trace context headers
TraceparentHeader = "traceparent"
TracestateHeader = "tracestate"
BaggageHeader = "baggage"
GRPCTraceContextKey = "grpc-trace-bin"
GRPCProxyAppIDKey = "dapr-app-id"
// Trace sampling constants
SupportedVersion = 0
MaxVersion = 254
MaxTracestateLen = 512
// MaxBaggageLength is the maximum length of a baggage header according to W3C spec
// Reverence: https://www.w3.org/TR/baggage/#limits
MaxBaggageLength = 8192
)
// GrpcAppendSpanAttributesFn is the interface that applies to gRPC requests that add span attributes.

View File

@ -25,6 +25,7 @@ import (
"google.golang.org/protobuf/proto"
"github.com/dapr/dapr/pkg/api/grpc/metadata"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
diagUtils "github.com/dapr/dapr/pkg/diagnostics/utils"
)
@ -264,7 +265,7 @@ func (g *grpcMetrics) StreamingServerInterceptor() grpc.StreamServerInterceptor
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
md, _ := metadata.FromIncomingContext(ctx)
vals, ok := md[GRPCProxyAppIDKey]
vals, ok := md[diagConsts.GRPCProxyAppIDKey]
if !ok || len(vals) == 0 {
return handler(srv, ss)
}
@ -285,7 +286,7 @@ func (g *grpcMetrics) StreamingClientInterceptor() grpc.StreamServerInterceptor
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := ss.Context()
md, _ := metadata.FromIncomingContext(ctx)
vals, ok := md[GRPCProxyAppIDKey]
vals, ok := md[diagConsts.GRPCProxyAppIDKey]
if !ok || len(vals) == 0 {
return handler(srv, ss)
}

View File

@ -25,6 +25,7 @@ import (
"github.com/dapr/dapr/pkg/api/grpc/metadata"
"github.com/dapr/dapr/pkg/config"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
)
type fakeProxyStream struct {
@ -41,7 +42,7 @@ func (f *fakeProxyStream) Context() context.Context {
}
ctx := context.Background()
ctx = grpcMetadata.NewIncomingContext(ctx, grpcMetadata.New(map[string]string{GRPCProxyAppIDKey: f.appID}))
ctx = grpcMetadata.NewIncomingContext(ctx, grpcMetadata.New(map[string]string{diagConsts.GRPCProxyAppIDKey: f.appID}))
ctx, _ = metadata.SetMetadataInTapHandle(ctx, nil)
return ctx
}

View File

@ -19,6 +19,7 @@ import (
"strings"
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware"
otelbaggage "go.opentelemetry.io/otel/baggage"
otelcodes "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
@ -33,8 +34,6 @@ import (
)
const (
GRPCTraceContextKey = "grpc-trace-bin"
GRPCProxyAppIDKey = "dapr-app-id"
daprInternalPrefix = "/dapr.proto.internals."
daprRuntimePrefix = "/dapr.proto.runtime."
daprInvokeServiceMethod = "/dapr.proto.runtime.v1.Dapr/InvokeService"
@ -42,6 +41,38 @@ const (
daprWorkflowPrefix = "/TaskHubSidecarService"
)
// handleBaggage processes baggage from incoming metadata, validates it, and updates the context accordingly.
func handleBaggage(ctx context.Context) context.Context {
md, ok := grpcMetadata.FromIncomingContext(ctx)
if !ok {
return ctx
}
baggageValues, exists := md[diagConsts.BaggageHeader]
if !exists {
return ctx
}
var validBaggage []string
for _, b := range baggageValues {
if diagUtils.IsValidBaggage(b) {
if member, err := otelbaggage.Parse(b); err == nil {
ctx = otelbaggage.ContextWithBaggage(ctx, member)
validBaggage = append(validBaggage, b)
}
}
}
if len(validBaggage) > 0 {
md[diagConsts.BaggageHeader] = []string{strings.Join(validBaggage, ",")}
} else {
// Remove the baggage header if no valid entries
delete(md, diagConsts.BaggageHeader)
}
return grpcMetadata.NewIncomingContext(ctx, md)
}
// GRPCTraceUnaryServerInterceptor sets the trace context or starts the trace client span based on request.
func GRPCTraceUnaryServerInterceptor(appID string, spec config.TracingSpec) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
@ -51,6 +82,14 @@ func GRPCTraceUnaryServerInterceptor(appID string, spec config.TracingSpec) grpc
prefixedMetadata map[string]string
reqSpanAttr map[string]string
)
ctx = handleBaggage(ctx)
// set header for outgoing response headers
md, _ := grpcMetadata.FromIncomingContext(ctx)
if baggage, exists := md[diagConsts.BaggageHeader]; exists {
grpc.SetHeader(ctx, grpcMetadata.Pairs(diagConsts.BaggageHeader, strings.Join(baggage, ",")))
}
sc, _ := SpanContextFromIncomingGRPCMetadata(ctx)
// This middleware is shared by internal gRPC for service invocation and API
// so that it needs to handle separately.
@ -89,7 +128,7 @@ func GRPCTraceUnaryServerInterceptor(appID string, spec config.TracingSpec) grpc
// Add grpc-trace-bin header for all non-invocation api's
if info.FullMethod != daprInvokeServiceMethod {
traceContextBinary := diagUtils.BinaryFromSpanContext(span.SpanContext())
grpc.SetHeader(ctx, grpcMetadata.Pairs(GRPCTraceContextKey, string(traceContextBinary)))
grpc.SetHeader(ctx, grpcMetadata.Pairs(diagConsts.GRPCTraceContextKey, string(traceContextBinary)))
}
UpdateSpanStatusFromGRPCError(span, err)
@ -110,7 +149,12 @@ func GRPCTraceStreamServerInterceptor(appID string, spec config.TracingSpec) grp
)
ctx := ss.Context()
ctx = handleBaggage(ctx)
// set header, ensures consistency with traceparent and tracestate headers
md, _ := grpcMetadata.FromIncomingContext(ctx)
if baggage, exists := md[diagConsts.BaggageHeader]; exists {
grpc.SetHeader(ctx, grpcMetadata.Pairs(diagConsts.BaggageHeader, strings.Join(baggage, ",")))
}
// This middleware is shared by multiple services and proxied requests, which need to be handled separately
switch {
// For gRPC service invocation, this generates ServerSpan
@ -129,9 +173,9 @@ func GRPCTraceStreamServerInterceptor(appID string, spec config.TracingSpec) grp
default:
isProxied = true
md, _ := metadata.FromIncomingContext(ctx)
vals := md.Get(GRPCProxyAppIDKey)
vals := md.Get(diagConsts.GRPCProxyAppIDKey)
if len(vals) == 0 {
return fmt.Errorf("cannot proxy request: missing %s metadata", GRPCProxyAppIDKey)
return fmt.Errorf("cannot proxy request: missing %s metadata", diagConsts.GRPCProxyAppIDKey)
}
// vals[0] is the target app ID
if appID == vals[0] {
@ -181,7 +225,7 @@ func GRPCTraceStreamServerInterceptor(appID string, spec config.TracingSpec) grp
// Add grpc-trace-bin header for all non-invocation api's
if !isProxied && info.FullMethod != daprInvokeServiceMethod {
traceContextBinary := diagUtils.BinaryFromSpanContext(span.SpanContext())
grpc.SetHeader(ctx, grpcMetadata.Pairs(GRPCTraceContextKey, string(traceContextBinary)))
grpc.SetHeader(ctx, grpcMetadata.Pairs(diagConsts.GRPCTraceContextKey, string(traceContextBinary)))
}
UpdateSpanStatusFromGRPCError(span, err)
@ -243,7 +287,7 @@ func SpanContextFromIncomingGRPCMetadata(ctx context.Context) (trace.SpanContext
if md, ok = metadata.FromIncomingContext(ctx); !ok {
return sc, false
}
traceContext := md[GRPCTraceContextKey]
traceContext := md[diagConsts.GRPCTraceContextKey]
if len(traceContext) > 0 {
sc, ok = diagUtils.SpanContextFromBinary([]byte(traceContext[0]))
} else {
@ -251,11 +295,11 @@ func SpanContextFromIncomingGRPCMetadata(ctx context.Context) (trace.SpanContext
// as grpc-trace-bin is not yet there in OpenTelemetry unlike OpenCensus , tracking issue https://github.com/open-telemetry/opentelemetry-specification/issues/639
// and grpc-dotnet client adheres to OpenTelemetry Spec which only supports http based traceparent header in gRPC path
// TODO : Remove this workaround fix once grpc-dotnet supports grpc-trace-bin header. Tracking issue https://github.com/dapr/dapr/issues/1827
traceContext = md[TraceparentHeader]
traceContext = md[diagConsts.TraceparentHeader]
if len(traceContext) > 0 {
sc, ok = SpanContextFromW3CString(traceContext[0])
if ok && len(md[TracestateHeader]) > 0 {
ts := TraceStateFromW3CString(md[TracestateHeader][0])
if ok && len(md[diagConsts.TracestateHeader]) > 0 {
ts := TraceStateFromW3CString(md[diagConsts.TracestateHeader][0])
sc.WithTraceState(*ts)
}
}
@ -270,7 +314,7 @@ func SpanContextToGRPCMetadata(ctx context.Context, spanContext trace.SpanContex
return ctx
}
return grpcMetadata.AppendToOutgoingContext(ctx, GRPCTraceContextKey, string(traceContextBinary))
return grpcMetadata.AppendToOutgoingContext(ctx, diagConsts.GRPCTraceContextKey, string(traceContextBinary))
}
// spanAttributesMapFromGRPC builds the span trace attributes map for gRPC calls based on given parameters as per open-telemetry specs.

View File

@ -24,6 +24,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
otelbaggage "go.opentelemetry.io/otel/baggage"
otelcodes "go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
@ -96,6 +97,171 @@ func TestSpanContextToGRPCMetadata(t *testing.T) {
})
}
func TestBaggageHeaderPropagation(t *testing.T) {
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "key1=value1,key2=value2",
))
ctx = handleBaggage(ctx)
// ensure baggage is in the ctx
baggage := otelbaggage.FromContext(ctx)
assert.NotNil(t, baggage)
member := baggage.Member("key1")
assert.Equal(t, "value1", member.Value())
member = baggage.Member("key2")
assert.Equal(t, "value2", member.Value())
}
// runBaggageHeaderPropagationTest runs the same baggage tests across both types of interceptors
func runBaggageHeaderPropagationTest(t *testing.T, interceptor interface{}) {
// handle both types of interceptors
var runInterceptor func(ctx context.Context) (context.Context, error)
fakeInfo := &grpc.UnaryServerInfo{
FullMethod: "/dapr.proto.runtime.v1.Dapr/GetState",
}
fakeReq := &runtimev1pb.GetStateRequest{
StoreName: "statestore",
Key: "state",
}
switch intercept := interceptor.(type) {
case grpc.UnaryServerInterceptor:
runInterceptor = func(ctx context.Context) (context.Context, error) {
var handlerCtx context.Context
assertHandler := func(ctx context.Context, req any) (any, error) {
handlerCtx = ctx
return nil, nil
}
_, err := intercept(ctx, fakeReq, fakeInfo, assertHandler)
return handlerCtx, err
}
case grpc.StreamServerInterceptor:
streamInfo := &grpc.StreamServerInfo{
FullMethod: fakeInfo.FullMethod,
}
runInterceptor = func(ctx context.Context) (context.Context, error) {
var handlerCtx context.Context
fakeStream := &fakeStream{ctx: ctx}
streamHandler := func(srv interface{}, stream grpc.ServerStream) error {
handlerCtx = stream.Context()
return nil
}
err := intercept(nil, fakeStream, streamInfo, streamHandler)
return handlerCtx, err
}
default:
t.Fatalf("Unsupported interceptor type %T", interceptor)
}
t.Run("baggage header propagation", func(t *testing.T) {
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "key1=value1",
))
handlerCtx, err := runInterceptor(ctx)
require.NoError(t, err)
// Verify baggage is in the context
baggage := otelbaggage.FromContext(handlerCtx)
assert.NotNil(t, baggage)
member := baggage.Member("key1")
assert.Equal(t, "value1", member.Value())
// Verify baggage header is set in incoming metadata
md, ok := grpcMetadata.FromIncomingContext(handlerCtx)
require.True(t, ok)
bag := md.Get(diagConsts.BaggageHeader)
require.NotEmpty(t, bag, "Expected baggage header to be set in metadata")
assert.Equal(t, "key1=value1", bag[0])
})
t.Run("empty baggage", func(t *testing.T) {
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "",
))
handlerCtx, err := runInterceptor(ctx)
require.NoError(t, err)
// Verify empty baggage is not propagated
md, ok := grpcMetadata.FromIncomingContext(handlerCtx)
require.True(t, ok)
assert.Empty(t, md.Get(diagConsts.BaggageHeader))
})
t.Run("baggage with properties", func(t *testing.T) {
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "key1=value1;prop1=propvalue1,key2=value2;prop2=propvalue2",
))
handlerCtx, err := runInterceptor(ctx)
require.NoError(t, err)
md, ok := grpcMetadata.FromIncomingContext(handlerCtx)
require.True(t, ok)
assert.Equal(t, "key1=value1;prop1=propvalue1,key2=value2;prop2=propvalue2", md.Get(diagConsts.BaggageHeader)[0])
})
t.Run("baggage with special characters", func(t *testing.T) {
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "key1=value1%20with%20spaces,key2=value2%2Fwith%2Fslashes",
))
handlerCtx, err := runInterceptor(ctx)
require.NoError(t, err)
md, ok := grpcMetadata.FromIncomingContext(handlerCtx)
require.True(t, ok)
assert.Equal(t, "key1=value1%20with%20spaces,key2=value2%2Fwith%2Fslashes", md.Get(diagConsts.BaggageHeader)[0])
})
t.Run("invalid baggage format", func(t *testing.T) {
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "invalid-baggage",
))
handlerCtx, err := runInterceptor(ctx)
require.NoError(t, err)
// invalid baggage should not be propagated in the new context
md, ok := grpcMetadata.FromIncomingContext(handlerCtx)
require.True(t, ok)
assert.Empty(t, md.Get(diagConsts.BaggageHeader))
})
t.Run("multiple baggage values in header", func(t *testing.T) {
// single baggage header with multiple values
ctx := grpcMetadata.NewIncomingContext(t.Context(), grpcMetadata.Pairs(
diagConsts.BaggageHeader, "key1=value1,key2=value2",
))
handlerCtx, err := runInterceptor(ctx)
require.NoError(t, err)
baggage := otelbaggage.FromContext(handlerCtx)
assert.NotNil(t, baggage)
member := baggage.Member("key1")
assert.Equal(t, "value1", member.Value())
member = baggage.Member("key2")
assert.Equal(t, "value2", member.Value())
// baggage headers are combined in metadata
md, ok := grpcMetadata.FromIncomingContext(handlerCtx)
require.True(t, ok)
bag := md.Get(diagConsts.BaggageHeader)
require.NotEmpty(t, bag)
assert.Equal(t, "key1=value1,key2=value2", bag[0])
})
}
func TestGRPCTraceUnaryServerInterceptor(t *testing.T) {
exp := newOtelFakeExporter()
@ -106,6 +272,7 @@ func TestGRPCTraceUnaryServerInterceptor(t *testing.T) {
otel.SetTracerProvider(tp)
interceptor := GRPCTraceUnaryServerInterceptor("fakeAppID", config.TracingSpec{SamplingRate: "1"})
runBaggageHeaderPropagationTest(t, interceptor)
testTraceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
testSpanContext, _ := SpanContextFromW3CString(testTraceParent)
@ -243,6 +410,7 @@ func TestGRPCTraceStreamServerInterceptor(t *testing.T) {
otel.SetTracerProvider(tp)
interceptor := GRPCTraceStreamServerInterceptor("test", config.TracingSpec{SamplingRate: "1"})
runBaggageHeaderPropagationTest(t, interceptor)
testTraceParent := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
testSpanContext, _ := SpanContextFromW3CString(testTraceParent)
@ -380,8 +548,8 @@ func TestGRPCTraceStreamServerInterceptor(t *testing.T) {
}
md := grpcMetadata.New(map[string]string{
GRPCProxyAppIDKey: "myapp",
"grpc-trace-bin": string(testTraceBinary),
diagConsts.GRPCProxyAppIDKey: "myapp",
"grpc-trace-bin": string(testTraceBinary),
})
ctx := grpcMetadata.NewIncomingContext(t.Context(), md)
ctx, _ = metadata.SetMetadataInTapHandle(ctx, nil)
@ -408,7 +576,7 @@ func TestGRPCTraceStreamServerInterceptor(t *testing.T) {
}
md := grpcMetadata.New(map[string]string{
GRPCProxyAppIDKey: "myapp",
diagConsts.GRPCProxyAppIDKey: "myapp",
})
ctx := grpcMetadata.NewIncomingContext(t.Context(), md)
ctx, _ = metadata.SetMetadataInTapHandle(ctx, nil)

View File

@ -42,7 +42,7 @@ func SpanContextToW3CString(sc trace.SpanContext) string {
spanID := sc.SpanID()
traceFlags := sc.TraceFlags()
return fmt.Sprintf("%x-%x-%x-%x",
[]byte{supportedVersion},
[]byte{diagConsts.SupportedVersion},
traceID[:],
spanID[:],
[]byte{byte(traceFlags)})
@ -71,7 +71,7 @@ func SpanContextFromW3CString(h string) (sc trace.SpanContext, ok bool) {
return trace.SpanContext{}, false
}
version := int(ver[0])
if version > maxVersion {
if version > diagConsts.MaxVersion {
return trace.SpanContext{}, false
}

View File

@ -25,9 +25,9 @@ import (
"github.com/dapr/dapr/pkg/acl"
grpcProxy "github.com/dapr/dapr/pkg/api/grpc/proxy"
codec "github.com/dapr/dapr/pkg/api/grpc/proxy/codec"
"github.com/dapr/dapr/pkg/api/grpc/proxy/codec"
"github.com/dapr/dapr/pkg/config"
"github.com/dapr/dapr/pkg/diagnostics"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
invokev1 "github.com/dapr/dapr/pkg/messaging/v1"
"github.com/dapr/dapr/pkg/proto/common/v1"
"github.com/dapr/dapr/pkg/resiliency"
@ -98,9 +98,9 @@ func nopTeardown(destroy bool) {
func (p *proxy) intercept(ctx context.Context, fullName string) (context.Context, *grpc.ClientConn, *grpcProxy.ProxyTarget, func(destroy bool), error) {
md, _ := metadata.FromIncomingContext(ctx)
v := md[diagnostics.GRPCProxyAppIDKey]
v := md[diagConsts.GRPCProxyAppIDKey]
if len(v) == 0 {
return ctx, nil, nil, nopTeardown, fmt.Errorf("failed to proxy request: required metadata %s not found", diagnostics.GRPCProxyAppIDKey)
return ctx, nil, nil, nopTeardown, fmt.Errorf("failed to proxy request: required metadata %s not found", diagConsts.GRPCProxyAppIDKey)
}
appID := v[0]

View File

@ -31,6 +31,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
diag "github.com/dapr/dapr/pkg/diagnostics"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
diagUtils "github.com/dapr/dapr/pkg/diagnostics/utils"
internalv1pb "github.com/dapr/dapr/pkg/proto/internals/v1"
)
@ -57,11 +58,6 @@ const (
// gRPCBinaryMetadata is the suffix of grpc metadata binary value.
gRPCBinaryMetadataSuffix = "-bin"
// W3C trace correlation headers.
traceparentHeader = "traceparent"
tracestateHeader = "tracestate"
tracebinMetadata = "grpc-trace-bin"
// DestinationIDHeader is the header carrying the value of the invoked app id.
DestinationIDHeader = "destination-app-id"
@ -149,13 +145,13 @@ func InternalMetadataToGrpcMetadata(ctx context.Context, internalMD DaprInternal
keyName := strings.ToLower(k)
// get both the trace headers for HTTP/GRPC and continue
switch keyName {
case traceparentHeader:
case diagConsts.TraceparentHeader:
traceparentValue = listVal.GetValues()[0]
continue
case tracestateHeader:
case diagConsts.TracestateHeader:
tracestateValue = listVal.GetValues()[0]
continue
case tracebinMetadata:
case diagConsts.GRPCTraceContextKey:
grpctracebinValue = listVal.GetValues()[0]
continue
case DestinationIDHeader:
@ -220,17 +216,20 @@ func InternalMetadataToHTTPHeader(ctx context.Context, internalMD DaprInternalMe
keyName := strings.ToLower(k)
// get both the trace headers for HTTP/GRPC and continue
switch keyName {
case traceparentHeader:
case diagConsts.TraceparentHeader:
traceparentValue = listVal.GetValues()[0]
continue
case tracestateHeader:
case diagConsts.TracestateHeader:
tracestateValue = listVal.GetValues()[0]
continue
case tracebinMetadata:
case diagConsts.GRPCTraceContextKey:
grpctracebinValue = listVal.GetValues()[0]
continue
case DestinationIDHeader:
continue
case diagConsts.BaggageHeader:
setHeader(diagConsts.BaggageHeader, listVal.GetValues()[0])
continue
}
if strings.HasSuffix(keyName, gRPCBinaryMetadataSuffix) || keyName == ContentTypeHeader {
@ -387,9 +386,9 @@ func processHTTPToHTTPTraceHeaders(ctx context.Context, traceparentValue, traceS
span := diagUtils.SpanFromContext(ctx)
diag.SpanContextToHTTPHeaders(span.SpanContext(), setHeader)
} else {
setHeader(traceparentHeader, traceparentValue)
setHeader(diagConsts.TraceparentHeader, traceparentValue)
if traceStateValue != "" {
setHeader(tracestateHeader, traceStateValue)
setHeader(diagConsts.TracestateHeader, traceStateValue)
}
}
}
@ -410,7 +409,7 @@ func processHTTPToGRPCTraceHeader(ctx context.Context, md metadata.MD, tracepare
diag.SpanContextToHTTPHeaders(sc, func(header, value string) {
md.Set(header, value)
})
md.Set(tracebinMetadata, string(diagUtils.BinaryFromSpanContext(sc)))
md.Set(diagConsts.GRPCTraceContextKey, string(diagUtils.BinaryFromSpanContext(sc)))
}
func processGRPCToGRPCTraceHeader(ctx context.Context, md metadata.MD, grpctracebinValue string) {
@ -424,7 +423,7 @@ func processGRPCToGRPCTraceHeader(ctx context.Context, md metadata.MD, grpctrace
diag.SpanContextToHTTPHeaders(sc, func(header, value string) {
md.Set(header, value)
})
md.Set(tracebinMetadata, string(diagUtils.BinaryFromSpanContext(sc)))
md.Set(diagConsts.GRPCTraceContextKey, string(diagUtils.BinaryFromSpanContext(sc)))
} else {
decoded, err := base64.StdEncoding.DecodeString(grpctracebinValue)
if err == nil {
@ -436,7 +435,7 @@ func processGRPCToGRPCTraceHeader(ctx context.Context, md metadata.MD, grpctrace
md.Set(header, value)
})
}
md.Set(tracebinMetadata, string(decoded))
md.Set(diagConsts.GRPCTraceContextKey, string(decoded))
}
}
}

View File

@ -33,6 +33,7 @@ import (
"github.com/dapr/dapr/pkg/components"
stateLoader "github.com/dapr/dapr/pkg/components/state"
diag "github.com/dapr/dapr/pkg/diagnostics"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
diagUtils "github.com/dapr/dapr/pkg/diagnostics/utils"
invokev1 "github.com/dapr/dapr/pkg/messaging/v1"
runtimev1pb "github.com/dapr/dapr/pkg/proto/runtime/v1"
@ -233,16 +234,16 @@ func (b *binding) sendBindingEventToApp(ctx context.Context, bindingName string,
// Check the grpc-trace-bin with fallback to traceparent.
validTraceparent := false
if val, ok := metadata[diag.GRPCTraceContextKey]; ok {
if val, ok := metadata[diagConsts.GRPCTraceContextKey]; ok {
if sc, ok := diagUtils.SpanContextFromBinary([]byte(val)); ok {
spanContext = sc
}
} else if val, ok := metadata[diag.TraceparentHeader]; ok {
} else if val, ok := metadata[diagConsts.TraceparentHeader]; ok {
if sc, ok := diag.SpanContextFromW3CString(val); ok {
spanContext = sc
validTraceparent = true
// Only parse the tracestate if we've successfully parsed the traceparent.
if val, ok := metadata[diag.TracestateHeader]; ok {
if val, ok := metadata[diagConsts.TracestateHeader]; ok {
ts := diag.TraceStateFromW3CString(val)
spanContext.WithTraceState(*ts)
}
@ -340,11 +341,13 @@ func (b *binding) sendBindingEventToApp(ctx context.Context, bindingName string,
for k, v := range metadata {
reqMetadata[k] = []string{v}
}
req := invokev1.NewInvokeMethodRequest(path).
WithHTTPExtension(http.MethodPost, "").
WithRawDataBytes(data).
WithContentType(invokev1.JSONContentType).
WithMetadata(reqMetadata)
if policyDef != nil {
req.WithReplay(policyDef.HasRetries())
}

View File

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
grpcMetadata "google.golang.org/grpc/metadata"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
"github.com/dapr/dapr/pkg/proto/runtime/v1"
"github.com/dapr/dapr/tests/integration/framework"
"github.com/dapr/dapr/tests/integration/framework/client"
@ -42,17 +43,24 @@ type output struct {
daprd *daprd.Daprd
traceparent atomic.Bool
baggage atomic.Bool
}
func (b *output) Setup(t *testing.T) []framework.Option {
handler := http.NewServeMux()
handler.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
if tp := r.Header.Get("traceparent"); tp != "" {
if tp := r.Header.Get(diagConsts.TraceparentHeader); tp != "" {
b.traceparent.Store(true)
} else {
b.traceparent.Store(false)
}
if baggage := r.Header.Get(diagConsts.BaggageHeader); baggage != "" {
b.baggage.Store(true)
} else {
b.baggage.Store(false)
}
w.Write([]byte(`OK`))
})
@ -83,7 +91,7 @@ func (b *output) Run(t *testing.T, ctx context.Context) {
httpClient := client.HTTP(t)
client := b.daprd.GRPCClient(t, ctx)
t.Run("no traceparent header provided", func(t *testing.T) {
t.Run("no traceparent header or baggage provided", func(t *testing.T) {
// invoke binding
reqURL := fmt.Sprintf("http://localhost:%d/v1.0/bindings/http-binding-traceparent", b.daprd.HTTPPort())
req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, strings.NewReader("{\"operation\":\"get\"}"))
@ -93,6 +101,7 @@ func (b *output) Run(t *testing.T, ctx context.Context) {
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.True(t, b.traceparent.Load())
assert.False(t, b.baggage.Load())
invokereq := runtime.InvokeBindingRequest{
Name: "http-binding-traceparent",
@ -104,9 +113,10 @@ func (b *output) Run(t *testing.T, ctx context.Context) {
require.NoError(t, err)
require.NotNil(t, invokeresp)
assert.True(t, b.traceparent.Load())
assert.False(t, b.baggage.Load())
})
t.Run("traceparent header provided", func(t *testing.T) {
t.Run("traceparent and baggage headers provided", func(t *testing.T) {
// invoke binding
ctx := t.Context()
reqURL := fmt.Sprintf("http://localhost:%d/v1.0/bindings/http-binding-traceparent", b.daprd.HTTPPort())
@ -114,13 +124,16 @@ func (b *output) Run(t *testing.T, ctx context.Context) {
require.NoError(t, err)
tp := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
req.Header.Set("traceparent", tp)
req.Header.Set(diagConsts.TraceparentHeader, tp)
bag := "key1=value1,key2=value2"
req.Header.Set(diagConsts.BaggageHeader, bag)
resp, err := httpClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.True(t, b.traceparent.Load())
assert.True(t, b.baggage.Load())
invokereq := runtime.InvokeBindingRequest{
Name: "http-binding-traceparent",
@ -129,10 +142,14 @@ func (b *output) Run(t *testing.T, ctx context.Context) {
// invoke binding
tp = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-02"
ctx = grpcMetadata.AppendToOutgoingContext(ctx, "traceparent", tp)
ctx = grpcMetadata.AppendToOutgoingContext(ctx,
diagConsts.TraceparentHeader, tp,
diagConsts.BaggageHeader, bag,
)
invokeresp, err := client.InvokeBinding(ctx, &invokereq)
require.NoError(t, err)
require.NotNil(t, invokeresp)
assert.True(t, b.traceparent.Load())
assert.True(t, b.baggage.Load())
})
}

View File

@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
grpcMetadata "google.golang.org/grpc/metadata"
diagConsts "github.com/dapr/dapr/pkg/diagnostics/consts"
"github.com/dapr/dapr/pkg/proto/common/v1"
"github.com/dapr/dapr/pkg/proto/runtime/v1"
"github.com/dapr/dapr/tests/integration/framework"
@ -47,16 +48,22 @@ type invoke struct {
traceparent atomic.Bool
grpctracectxkey atomic.Bool
baggage atomic.Bool
}
func (i *invoke) Setup(t *testing.T) []framework.Option {
handler := http.NewServeMux()
handler.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
if tp := r.Header.Get("traceparent"); tp != "" {
if tp := r.Header.Get(diagConsts.TraceparentHeader); tp != "" {
i.traceparent.Store(true)
} else {
i.traceparent.Store(false)
}
if baggage := r.Header.Get(diagConsts.BaggageHeader); baggage != "" {
i.baggage.Store(true)
} else {
i.baggage.Store(false)
}
w.Write([]byte(`OK`))
})
@ -67,11 +74,16 @@ func (i *invoke) Setup(t *testing.T) []framework.Option {
switch in.GetMethod() {
case "test":
if md, ok := grpcMetadata.FromIncomingContext(ctx); ok {
if _, exists := md["grpc-trace-bin"]; exists {
if _, exists := md[diagConsts.GRPCTraceContextKey]; exists {
i.grpctracectxkey.Store(true)
} else {
i.grpctracectxkey.Store(false)
}
if _, exists := md[diagConsts.BaggageHeader]; exists {
i.baggage.Store(true)
} else {
i.baggage.Store(false)
}
}
}
return nil, nil
@ -94,7 +106,7 @@ func (i *invoke) Run(t *testing.T, ctx context.Context) {
httpClient := client.HTTP(t)
client := i.daprd.GRPCClient(t, ctx)
t.Run("no traceparent header provided", func(t *testing.T) {
t.Run("no traceparent header or baggage provided", func(t *testing.T) {
// invoke both grpc & http apps
appURL := fmt.Sprintf("http://localhost:%d/v1.0/invoke/%s/method/test", i.daprd.HTTPPort(), i.daprd.AppID())
appreq, err := http.NewRequestWithContext(ctx, http.MethodPost, appURL, strings.NewReader("{\"operation\":\"get\"}"))
@ -104,6 +116,7 @@ func (i *invoke) Run(t *testing.T, ctx context.Context) {
defer appresp.Body.Close()
assert.Equal(t, http.StatusOK, appresp.StatusCode)
assert.True(t, i.traceparent.Load())
assert.False(t, i.baggage.Load())
svcreq := runtime.InvokeServiceRequest{
Id: i.daprd.AppID(),
@ -122,6 +135,7 @@ func (i *invoke) Run(t *testing.T, ctx context.Context) {
require.NoError(t, err)
require.NotNil(t, svcresp)
assert.True(t, i.traceparent.Load())
assert.False(t, i.baggage.Load())
grpcappreq := runtime.InvokeServiceRequest{
Id: i.grpcdaprd.AppID(),
@ -142,22 +156,26 @@ func (i *invoke) Run(t *testing.T, ctx context.Context) {
require.NoError(t, err)
require.NotNil(t, svcresp)
assert.True(t, i.grpctracectxkey.Load()) // this is set for grpc, instead of traceparent
assert.False(t, i.baggage.Load())
})
t.Run("traceparent header provided", func(t *testing.T) {
t.Run("traceparent and baggage headers provided", func(t *testing.T) {
// invoke both grpc & http apps
appURL := fmt.Sprintf("http://localhost:%d/v1.0/invoke/%s/method/test", i.daprd.HTTPPort(), i.daprd.AppID())
appreq, err := http.NewRequestWithContext(ctx, http.MethodPost, appURL, strings.NewReader("{\"operation\":\"get\"}"))
require.NoError(t, err)
tp := "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
appreq.Header.Set("traceparent", tp)
appreq.Header.Set(diagConsts.TraceparentHeader, tp)
bag := "key1=value1,key2=value2"
appreq.Header.Set(diagConsts.BaggageHeader, bag)
appresp, err := httpClient.Do(appreq)
require.NoError(t, err)
defer appresp.Body.Close()
assert.Equal(t, http.StatusOK, appresp.StatusCode)
assert.True(t, i.traceparent.Load())
assert.True(t, i.baggage.Load())
svcreq := runtime.InvokeServiceRequest{
Id: i.daprd.AppID(),
@ -173,11 +191,15 @@ func (i *invoke) Run(t *testing.T, ctx context.Context) {
}
tp = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-02"
tctx := grpcMetadata.AppendToOutgoingContext(ctx, "traceparent", tp)
tctx := grpcMetadata.AppendToOutgoingContext(ctx,
diagConsts.TraceparentHeader, tp,
diagConsts.BaggageHeader, bag,
)
svcresp, err := client.InvokeService(tctx, &svcreq)
require.NoError(t, err)
require.NotNil(t, svcresp)
assert.True(t, i.traceparent.Load())
assert.True(t, i.baggage.Load())
grpcappreq := runtime.InvokeServiceRequest{
Id: i.grpcdaprd.AppID(),
@ -193,11 +215,15 @@ func (i *invoke) Run(t *testing.T, ctx context.Context) {
}
tp = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03"
tctx = grpcMetadata.AppendToOutgoingContext(tctx, "traceparent", tp)
tctx = grpcMetadata.AppendToOutgoingContext(tctx,
diagConsts.TraceparentHeader, tp,
diagConsts.BaggageHeader, bag,
)
grpcclient := i.grpcdaprd.GRPCClient(t, tctx)
svcresp, err = grpcclient.InvokeService(tctx, &grpcappreq)
require.NoError(t, err)
require.NotNil(t, svcresp)
assert.True(t, i.grpctracectxkey.Load()) // this is set for grpc, instead of traceparent
assert.True(t, i.baggage.Load())
})
}

View File

@ -15,6 +15,7 @@ limitations under the License.
package tracing
import (
_ "github.com/dapr/dapr/tests/integration/suite/daprd/tracing/baggage"
_ "github.com/dapr/dapr/tests/integration/suite/daprd/tracing/binding"
_ "github.com/dapr/dapr/tests/integration/suite/daprd/tracing/serviceinvocation"
)