From 6571ab6161e24b18b70754962acbe0078111f2d3 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Fri, 6 Dec 2024 12:56:33 -0500 Subject: [PATCH 1/8] Add span pointer attributes to lifecycle --- pkg/serverless/invocationlifecycle/init.go | 32 +++++++++++++++++-- .../invocationlifecycle/lifecycle.go | 10 +++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/pkg/serverless/invocationlifecycle/init.go b/pkg/serverless/invocationlifecycle/init.go index e572696ae7d2f..1f7958f9ef8d4 100644 --- a/pkg/serverless/invocationlifecycle/init.go +++ b/pkg/serverless/invocationlifecycle/init.go @@ -6,6 +6,8 @@ package invocationlifecycle import ( + "crypto/sha256" + "encoding/hex" "fmt" "strings" "time" @@ -22,6 +24,7 @@ import ( const ( tagFunctionTriggerEventSource = "function_trigger.event_source" tagFunctionTriggerEventSourceArn = "function_trigger.event_source_arn" + s3PointerKind = "aws.s3.object" ) func (lp *LifecycleProcessor) initFromAPIGatewayEvent(event events.APIGatewayProxyRequest, region string) { @@ -126,8 +129,11 @@ func (lp *LifecycleProcessor) initFromKinesisStreamEvent(event events.KinesisEve } func (lp *LifecycleProcessor) initFromS3Event(event events.S3Event) { - if !lp.DetectLambdaLibrary() && lp.InferredSpansEnabled { - lp.GetInferredSpan().EnrichInferredSpanWithS3Event(event) + if !lp.DetectLambdaLibrary() { + if lp.InferredSpansEnabled { + lp.GetInferredSpan().EnrichInferredSpanWithS3Event(event) + } + lp.addSpanPointersFromS3Event(event) } lp.requestHandler.event = event @@ -224,3 +230,25 @@ func (lp *LifecycleProcessor) initFromLambdaFunctionURLEvent(event events.Lambda func (lp *LifecycleProcessor) initFromStepFunctionPayload(event events.StepFunctionPayload) { lp.requestHandler.event = event } + +func (lp *LifecycleProcessor) addSpanPointersFromS3Event(event events.S3Event) { + for _, record := range event.Records { + bucketName := record.S3.Bucket.Name + key := record.S3.Object.Key + eTag := strings.Trim(record.S3.Object.ETag, "\"") + + hash := generateSpanPointerHash([]string{bucketName, key, eTag}) + + spanPointer := SpanPointer{ + Hash: hash, + Kind: s3PointerKind, + } + lp.requestHandler.spanPointers = []SpanPointer{spanPointer} + } +} + +func generateSpanPointerHash(components []string) string { + dataToHash := strings.Join(components, "|") + sum := sha256.Sum256([]byte(dataToHash)) + return hex.EncodeToString(sum[:])[:32] +} diff --git a/pkg/serverless/invocationlifecycle/lifecycle.go b/pkg/serverless/invocationlifecycle/lifecycle.go index fec26be6d0de1..0f4317248ea21 100644 --- a/pkg/serverless/invocationlifecycle/lifecycle.go +++ b/pkg/serverless/invocationlifecycle/lifecycle.go @@ -42,7 +42,7 @@ type LifecycleProcessor struct { } // RequestHandler is the struct that stores information about the trace, -// inferred span, and tags about the current invocation +// inferred span, tags about the current invocation, and span pointers // inferred spans may contain a secondary inferred span in certain cases like SNS from SQS type RequestHandler struct { executionInfo *ExecutionStartInfo @@ -50,6 +50,14 @@ type RequestHandler struct { inferredSpans [2]*inferredspan.InferredSpan triggerTags map[string]string triggerMetrics map[string]float64 + spanPointers []SpanPointer +} + +// SpanPointer is a struct that stores a hash and span kind to uniquely +// identify a S3 or DynamoDB operation. +type SpanPointer struct { + Hash string + Kind string } // SetMetaTag sets a meta span tag. A meta tag is a tag whose value type is string. From 4200fa41d3dd20a59dc67bb8b484d99a42e2159f Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Fri, 6 Dec 2024 12:57:13 -0500 Subject: [PATCH 2/8] Set span pointer JSON in span meta --- pkg/serverless/invocationlifecycle/trace.go | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pkg/serverless/invocationlifecycle/trace.go b/pkg/serverless/invocationlifecycle/trace.go index 7603dfd017445..d966cb5a887a6 100644 --- a/pkg/serverless/invocationlifecycle/trace.go +++ b/pkg/serverless/invocationlifecycle/trace.go @@ -28,7 +28,9 @@ import ( ) const ( - functionNameEnvVar = "AWS_LAMBDA_FUNCTION_NAME" + functionNameEnvVar = "AWS_LAMBDA_FUNCTION_NAME" + spanPointerLinkKind = "span-pointer" + spanPointerUpDirection = "u" ) var /* const */ runtimeRegex = regexp.MustCompile(`^(dotnet|go|java|ruby)(\d+(\.\d+)*|\d+(\.x))$`) @@ -162,6 +164,29 @@ func (lp *LifecycleProcessor) endExecutionSpan(endDetails *InvocationEndDetails) } } + if len(lp.requestHandler.spanPointers) > 0 { + var spanLinks []map[string]interface{} + for _, sp := range lp.requestHandler.spanPointers { + spanLink := map[string]interface{}{ + "attributes": map[string]string{ + "link.kind": spanPointerLinkKind, + "ptr.dir": spanPointerUpDirection, + "ptr.hash": sp.Hash, + "ptr.kind": sp.Kind, + }, + "span_id": "0", + "trace_id": "0", + } + spanLinks = append(spanLinks, spanLink) + } + spanLinksJSON, err := json.Marshal(spanLinks) + if err != nil { + log.Debugf("Failed to marshal span links: %v\n", err) + } else { + executionSpan.Meta["_dd.span_links"] = string(spanLinksJSON) + } + } + return executionSpan } From 9e1e14c611697ed77b8ac8498e9db6736d203be2 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 11 Dec 2024 11:32:24 -0500 Subject: [PATCH 3/8] Fix lifecycle_test.go --- .../invocationlifecycle/lifecycle_test.go | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pkg/serverless/invocationlifecycle/lifecycle_test.go b/pkg/serverless/invocationlifecycle/lifecycle_test.go index 3eefd8dc51bbd..e62db6ce84115 100644 --- a/pkg/serverless/invocationlifecycle/lifecycle_test.go +++ b/pkg/serverless/invocationlifecycle/lifecycle_test.go @@ -7,6 +7,7 @@ package invocationlifecycle import ( "bytes" + "encoding/json" "os" "testing" "time" @@ -1026,12 +1027,30 @@ func TestTriggerTypesLifecycleEventForS3(t *testing.T) { testProcessor.OnInvokeEnd(&InvocationEndDetails{ RequestID: "test-request-id", }) - assert.Equal(t, map[string]string{ - "cold_start": "false", - "function_trigger.event_source_arn": "aws:s3:sample:event:source", - "request_id": "test-request-id", - "function_trigger.event_source": "s3", - }, testProcessor.GetTags()) + + tags := testProcessor.GetTags() + assert.Equal(t, "false", tags["cold_start"]) + assert.Equal(t, "aws:s3:sample:event:source", tags["function_trigger.event_source_arn"]) + assert.Equal(t, "test-request-id", tags["request_id"]) + assert.Equal(t, "s3", tags["function_trigger.event_source"]) + + var actualSpanLinks []map[string]interface{} + err := json.Unmarshal([]byte(tags["_dd.span_links"]), &actualSpanLinks) + assert.NoError(t, err) + + expectedSpanLinks := []map[string]interface{}{ + { + "attributes": map[string]interface{}{ + "link.kind": "span-pointer", + "ptr.dir": "u", + "ptr.hash": "1dc3e5d00dae48c1f07d95371a747788", + "ptr.kind": "aws.s3.object", + }, + "span_id": "0", + "trace_id": "0", + }, + } + assert.Equal(t, expectedSpanLinks, actualSpanLinks) } func TestTriggerTypesLifecycleEventForSNS(t *testing.T) { From 2d2ee0df5060f0a7109f65b419b5301c4cb54316 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 11 Dec 2024 11:35:42 -0500 Subject: [PATCH 4/8] Unit tests for setting span pointer attributes + generatePointerHash() --- .../invocationlifecycle/init_test.go | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 pkg/serverless/invocationlifecycle/init_test.go diff --git a/pkg/serverless/invocationlifecycle/init_test.go b/pkg/serverless/invocationlifecycle/init_test.go new file mode 100644 index 0000000000000..9a611ff2cd689 --- /dev/null +++ b/pkg/serverless/invocationlifecycle/init_test.go @@ -0,0 +1,144 @@ +// 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 2016-present Datadog, Inc. + +package invocationlifecycle + +import ( + "github.com/DataDog/datadog-agent/pkg/serverless/trigger/events" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGenerateSpanPointerHash(t *testing.T) { + tests := []struct { + name string + components []string + expectedHash string + }{ + { + name: "basic values", + components: []string{"some-bucket", "some-key.data", "ab12ef34"}, + expectedHash: "e721375466d4116ab551213fdea08413", + }, + { + name: "non-ascii key", + components: []string{"some-bucket", "some-key.你好", "ab12ef34"}, + expectedHash: "d1333a04b9928ab462b5c6cadfa401f4", + }, + { + name: "multipart-upload", + components: []string{"some-bucket", "some-key.data", "ab12ef34-5"}, + expectedHash: "2b90dffc37ebc7bc610152c3dc72af9f", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actualHash := generateSpanPointerHash(tt.components) + assert.Equal(t, tt.expectedHash, actualHash) + }) + } +} + +func TestAddSpanPointersFromS3Event(t *testing.T) { + tests := []struct { + name string + event events.S3Event + expectedSpanPointers []SpanPointer + }{ + { + name: "single record", + event: events.S3Event{ + Records: []events.S3EventRecord{ + { + S3: events.S3Entity{ + Bucket: events.S3Bucket{Name: "test-bucket"}, + Object: events.S3Object{ + Key: "test-key.data", + ETag: "\"abc\"", + }, + }, + }, + }, + }, + expectedSpanPointers: []SpanPointer{ + { + Hash: "1e2a4f250ca4e4de7eab7da3b672515a", + Kind: s3PointerKind, + }, + }, + }, + { + name: "single record with no etag quotes", + event: events.S3Event{ + Records: []events.S3EventRecord{ + { + S3: events.S3Entity{ + Bucket: events.S3Bucket{Name: "test-bucket"}, + Object: events.S3Object{ + Key: "test-key.data", + ETag: "abc", + }, + }, + }, + }, + }, + expectedSpanPointers: []SpanPointer{ + { + Hash: "1e2a4f250ca4e4de7eab7da3b672515a", + Kind: s3PointerKind, + }, + }, + }, + { + name: "multiple invocations - should only keep most recent pointer", + event: events.S3Event{ + Records: []events.S3EventRecord{ + { + S3: events.S3Entity{ + Bucket: events.S3Bucket{Name: "test-bucket"}, + Object: events.S3Object{ + Key: "test-key", + ETag: "\"123456\"", + }, + }, + }, + { + S3: events.S3Entity{ + Bucket: events.S3Bucket{Name: "some-bucket"}, + Object: events.S3Object{ + Key: "some-key.data", + ETag: "\"ab12ef34\"", + }, + }, + }, + }, + }, + expectedSpanPointers: []SpanPointer{ + { + Hash: "e721375466d4116ab551213fdea08413", + Kind: s3PointerKind, + }, + }, + }, + { + name: "empty event", + event: events.S3Event{}, + expectedSpanPointers: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + lp := &LifecycleProcessor{ + requestHandler: &RequestHandler{}, + } + + lp.addSpanPointersFromS3Event(tt.event) + + assert.Equal(t, tt.expectedSpanPointers, lp.requestHandler.spanPointers) + }) + } +} From 90cd1781f5edb0b7bd6f458d39a4a7b74a573503 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 11 Dec 2024 13:25:04 -0500 Subject: [PATCH 5/8] Unit tests for setting span pointer JSON in span meta --- .../invocationlifecycle/trace_test.go | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/pkg/serverless/invocationlifecycle/trace_test.go b/pkg/serverless/invocationlifecycle/trace_test.go index 597b986992c8f..f37e9f391132a 100644 --- a/pkg/serverless/invocationlifecycle/trace_test.go +++ b/pkg/serverless/invocationlifecycle/trace_test.go @@ -6,6 +6,7 @@ package invocationlifecycle import ( + "encoding/json" "net/http" "testing" "time" @@ -809,6 +810,92 @@ func TestEndExecutionSpanWithStepFunctions(t *testing.T) { } +func TestEndExecutionSpanWithSpanLinks(t *testing.T) { + tests := []struct { + name string + spanPointers []SpanPointer + expectedHashes []string + }{ + { + name: "single span pointer", + spanPointers: []SpanPointer{ + { + Hash: "abc", + Kind: "aws.s3.object", + }, + }, + expectedHashes: []string{"abc"}, + }, + { + name: "multiple span pointers", + spanPointers: []SpanPointer{ + { + Hash: "def", + Kind: "aws.s3.object", + }, + { + Hash: "ghi", + Kind: "aws.s3.object", + }, + }, + expectedHashes: []string{"def", "ghi"}, + }, + { + name: "no span pointers", + spanPointers: nil, + expectedHashes: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + currentExecutionInfo := &ExecutionStartInfo{} + lp := &LifecycleProcessor{ + requestHandler: &RequestHandler{ + executionInfo: currentExecutionInfo, + spanPointers: tt.spanPointers, + triggerTags: make(map[string]string), + }, + } + + startTime := time.Now() + startDetails := &InvocationStartDetails{ + StartTime: startTime, + InvokeEventHeaders: http.Header{}, + } + lp.startExecutionSpan(nil, []byte("{}"), startDetails) + + endDetails := &InvocationEndDetails{ + EndTime: startTime.Add(1 * time.Second), + RequestID: "test-request-id", + ResponseRawPayload: []byte("{}"), + } + + executionSpan := lp.endExecutionSpan(endDetails) + + if len(tt.expectedHashes) == 0 { + assert.NotContains(t, executionSpan.Meta, "_dd.span_links") + return + } + + var spanLinks []map[string]interface{} + err := json.Unmarshal([]byte(executionSpan.Meta["_dd.span_links"]), &spanLinks) + assert.NoError(t, err) + assert.Equal(t, len(tt.expectedHashes), len(spanLinks)) + for i, spanLink := range spanLinks { + attributes, ok := spanLink["attributes"].(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, tt.expectedHashes[i], attributes["ptr.hash"]) + assert.Equal(t, "span-pointer", attributes["link.kind"]) + assert.Equal(t, "u", attributes["ptr.dir"]) + assert.Equal(t, "aws.s3.object", attributes["ptr.kind"]) + assert.Equal(t, "0", spanLink["span_id"]) + assert.Equal(t, "0", spanLink["trace_id"]) + } + }) + } +} + func TestParseLambdaPayload(t *testing.T) { assert.Equal(t, []byte(""), ParseLambdaPayload([]byte(""))) assert.Equal(t, []byte("{}"), ParseLambdaPayload([]byte("{}"))) From 20c1a104baabfeae68de14bae1dfacf6729b185d Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Wed, 11 Dec 2024 13:48:46 -0500 Subject: [PATCH 6/8] add reno changelog --- .../notes/span-pointers-s3-a882b69be90214ea.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 releasenotes/notes/span-pointers-s3-a882b69be90214ea.yaml diff --git a/releasenotes/notes/span-pointers-s3-a882b69be90214ea.yaml b/releasenotes/notes/span-pointers-s3-a882b69be90214ea.yaml new file mode 100644 index 0000000000000..685b7c6702556 --- /dev/null +++ b/releasenotes/notes/span-pointers-s3-a882b69be90214ea.yaml @@ -0,0 +1,11 @@ +# Each section from every release note are combined when the +# CHANGELOG.rst is rendered. So the text needs to be worded so that +# it does not depend on any information only available in another +# section. This may mean repeating some details, but each section +# must be readable independently of the other. +# +# Each section note must be formatted as reStructuredText. +--- +features: + - | + Add span pointers for Lambdas triggered by S3 update events. From f989f53b101e41f7c459ffa955835788e54ab410 Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Fri, 13 Dec 2024 13:22:29 -0500 Subject: [PATCH 7/8] refactor helper functions into span_pointers.go --- pkg/serverless/invocationlifecycle/init.go | 28 +----------- .../invocationlifecycle/lifecycle.go | 10 +---- pkg/serverless/invocationlifecycle/trace.go | 2 +- .../invocationlifecycle/trace_test.go | 7 +-- pkg/serverless/spanpointers/span_pointers.go | 43 +++++++++++++++++++ .../span_pointers_test.go} | 18 ++++---- 6 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 pkg/serverless/spanpointers/span_pointers.go rename pkg/serverless/{invocationlifecycle/init_test.go => spanpointers/span_pointers_test.go} (89%) diff --git a/pkg/serverless/invocationlifecycle/init.go b/pkg/serverless/invocationlifecycle/init.go index 1f7958f9ef8d4..b399000054fd9 100644 --- a/pkg/serverless/invocationlifecycle/init.go +++ b/pkg/serverless/invocationlifecycle/init.go @@ -6,9 +6,8 @@ package invocationlifecycle import ( - "crypto/sha256" - "encoding/hex" "fmt" + "github.com/DataDog/datadog-agent/pkg/serverless/spanpointers" "strings" "time" @@ -24,7 +23,6 @@ import ( const ( tagFunctionTriggerEventSource = "function_trigger.event_source" tagFunctionTriggerEventSourceArn = "function_trigger.event_source_arn" - s3PointerKind = "aws.s3.object" ) func (lp *LifecycleProcessor) initFromAPIGatewayEvent(event events.APIGatewayProxyRequest, region string) { @@ -133,7 +131,7 @@ func (lp *LifecycleProcessor) initFromS3Event(event events.S3Event) { if lp.InferredSpansEnabled { lp.GetInferredSpan().EnrichInferredSpanWithS3Event(event) } - lp.addSpanPointersFromS3Event(event) + lp.requestHandler.spanPointers = spanpointers.GetSpanPointersFromS3Event(event) } lp.requestHandler.event = event @@ -230,25 +228,3 @@ func (lp *LifecycleProcessor) initFromLambdaFunctionURLEvent(event events.Lambda func (lp *LifecycleProcessor) initFromStepFunctionPayload(event events.StepFunctionPayload) { lp.requestHandler.event = event } - -func (lp *LifecycleProcessor) addSpanPointersFromS3Event(event events.S3Event) { - for _, record := range event.Records { - bucketName := record.S3.Bucket.Name - key := record.S3.Object.Key - eTag := strings.Trim(record.S3.Object.ETag, "\"") - - hash := generateSpanPointerHash([]string{bucketName, key, eTag}) - - spanPointer := SpanPointer{ - Hash: hash, - Kind: s3PointerKind, - } - lp.requestHandler.spanPointers = []SpanPointer{spanPointer} - } -} - -func generateSpanPointerHash(components []string) string { - dataToHash := strings.Join(components, "|") - sum := sha256.Sum256([]byte(dataToHash)) - return hex.EncodeToString(sum[:])[:32] -} diff --git a/pkg/serverless/invocationlifecycle/lifecycle.go b/pkg/serverless/invocationlifecycle/lifecycle.go index 0f4317248ea21..9c5dda6f12ce2 100644 --- a/pkg/serverless/invocationlifecycle/lifecycle.go +++ b/pkg/serverless/invocationlifecycle/lifecycle.go @@ -18,6 +18,7 @@ import ( pb "github.com/DataDog/datadog-agent/pkg/proto/pbgo/trace" serverlessLog "github.com/DataDog/datadog-agent/pkg/serverless/logs" serverlessMetrics "github.com/DataDog/datadog-agent/pkg/serverless/metrics" + "github.com/DataDog/datadog-agent/pkg/serverless/spanpointers" "github.com/DataDog/datadog-agent/pkg/serverless/trace/inferredspan" "github.com/DataDog/datadog-agent/pkg/serverless/trace/propagation" "github.com/DataDog/datadog-agent/pkg/serverless/trigger" @@ -50,14 +51,7 @@ type RequestHandler struct { inferredSpans [2]*inferredspan.InferredSpan triggerTags map[string]string triggerMetrics map[string]float64 - spanPointers []SpanPointer -} - -// SpanPointer is a struct that stores a hash and span kind to uniquely -// identify a S3 or DynamoDB operation. -type SpanPointer struct { - Hash string - Kind string + spanPointers []spanpointers.SpanPointer } // SetMetaTag sets a meta span tag. A meta tag is a tag whose value type is string. diff --git a/pkg/serverless/invocationlifecycle/trace.go b/pkg/serverless/invocationlifecycle/trace.go index d966cb5a887a6..5e3e1b03fc54a 100644 --- a/pkg/serverless/invocationlifecycle/trace.go +++ b/pkg/serverless/invocationlifecycle/trace.go @@ -30,7 +30,7 @@ import ( const ( functionNameEnvVar = "AWS_LAMBDA_FUNCTION_NAME" spanPointerLinkKind = "span-pointer" - spanPointerUpDirection = "u" + spanPointerUpDirection = "u" // Tracers will handle cases where direction is down ) var /* const */ runtimeRegex = regexp.MustCompile(`^(dotnet|go|java|ruby)(\d+(\.\d+)*|\d+(\.x))$`) diff --git a/pkg/serverless/invocationlifecycle/trace_test.go b/pkg/serverless/invocationlifecycle/trace_test.go index f37e9f391132a..b429ca90defc8 100644 --- a/pkg/serverless/invocationlifecycle/trace_test.go +++ b/pkg/serverless/invocationlifecycle/trace_test.go @@ -7,6 +7,7 @@ package invocationlifecycle import ( "encoding/json" + "github.com/DataDog/datadog-agent/pkg/serverless/spanpointers" "net/http" "testing" "time" @@ -813,12 +814,12 @@ func TestEndExecutionSpanWithStepFunctions(t *testing.T) { func TestEndExecutionSpanWithSpanLinks(t *testing.T) { tests := []struct { name string - spanPointers []SpanPointer + spanPointers []spanpointers.SpanPointer expectedHashes []string }{ { name: "single span pointer", - spanPointers: []SpanPointer{ + spanPointers: []spanpointers.SpanPointer{ { Hash: "abc", Kind: "aws.s3.object", @@ -828,7 +829,7 @@ func TestEndExecutionSpanWithSpanLinks(t *testing.T) { }, { name: "multiple span pointers", - spanPointers: []SpanPointer{ + spanPointers: []spanpointers.SpanPointer{ { Hash: "def", Kind: "aws.s3.object", diff --git a/pkg/serverless/spanpointers/span_pointers.go b/pkg/serverless/spanpointers/span_pointers.go new file mode 100644 index 0000000000000..1aa315538e3b8 --- /dev/null +++ b/pkg/serverless/spanpointers/span_pointers.go @@ -0,0 +1,43 @@ +package spanpointers + +import ( + "crypto/sha256" + "encoding/hex" + "github.com/DataDog/datadog-agent/pkg/serverless/trigger/events" + "strings" +) + +const ( + s3PointerKind = "aws.s3.object" +) + +// SpanPointer is a struct that stores a hash and span kind to uniquely +// identify a S3 or DynamoDB operation. +type SpanPointer struct { + Hash string + Kind string +} + +func generateSpanPointerHash(components []string) string { + dataToHash := strings.Join(components, "|") + sum := sha256.Sum256([]byte(dataToHash)) + return hex.EncodeToString(sum[:])[:32] +} + +func GetSpanPointersFromS3Event(event events.S3Event) []SpanPointer { + var pointers []SpanPointer + for _, record := range event.Records { + bucketName := record.S3.Bucket.Name + key := record.S3.Object.Key + eTag := strings.Trim(record.S3.Object.ETag, "\"") + + hash := generateSpanPointerHash([]string{bucketName, key, eTag}) + + spanPointer := SpanPointer{ + Hash: hash, + Kind: s3PointerKind, + } + pointers = append(pointers, spanPointer) + } + return pointers +} diff --git a/pkg/serverless/invocationlifecycle/init_test.go b/pkg/serverless/spanpointers/span_pointers_test.go similarity index 89% rename from pkg/serverless/invocationlifecycle/init_test.go rename to pkg/serverless/spanpointers/span_pointers_test.go index 9a611ff2cd689..7fc0654204d6f 100644 --- a/pkg/serverless/invocationlifecycle/init_test.go +++ b/pkg/serverless/spanpointers/span_pointers_test.go @@ -3,7 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). // Copyright 2016-present Datadog, Inc. -package invocationlifecycle +package spanpointers import ( "github.com/DataDog/datadog-agent/pkg/serverless/trigger/events" @@ -42,7 +42,7 @@ func TestGenerateSpanPointerHash(t *testing.T) { } } -func TestAddSpanPointersFromS3Event(t *testing.T) { +func TestGetSpanPointersFromS3Event(t *testing.T) { tests := []struct { name string event events.S3Event @@ -93,7 +93,7 @@ func TestAddSpanPointersFromS3Event(t *testing.T) { }, }, { - name: "multiple invocations - should only keep most recent pointer", + name: "multiple invocations", event: events.S3Event{ Records: []events.S3EventRecord{ { @@ -117,6 +117,10 @@ func TestAddSpanPointersFromS3Event(t *testing.T) { }, }, expectedSpanPointers: []SpanPointer{ + { + Hash: "1294423cd905a1041b4cda23022e476a", + Kind: s3PointerKind, + }, { Hash: "e721375466d4116ab551213fdea08413", Kind: s3PointerKind, @@ -132,13 +136,7 @@ func TestAddSpanPointersFromS3Event(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - lp := &LifecycleProcessor{ - requestHandler: &RequestHandler{}, - } - - lp.addSpanPointersFromS3Event(tt.event) - - assert.Equal(t, tt.expectedSpanPointers, lp.requestHandler.spanPointers) + assert.Equal(t, tt.expectedSpanPointers, GetSpanPointersFromS3Event(tt.event)) }) } } From f80439687609b5d92f167e7c0481b49793a513fe Mon Sep 17 00:00:00 2001 From: Nicholas Hulston Date: Fri, 13 Dec 2024 13:59:49 -0500 Subject: [PATCH 8/8] lint --- pkg/serverless/spanpointers/span_pointers.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/serverless/spanpointers/span_pointers.go b/pkg/serverless/spanpointers/span_pointers.go index 1aa315538e3b8..f342bfad3d2fb 100644 --- a/pkg/serverless/spanpointers/span_pointers.go +++ b/pkg/serverless/spanpointers/span_pointers.go @@ -1,3 +1,9 @@ +// 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 2016-present Datadog, Inc. + +// Package spanpointers provides a helper functions for span pointers package spanpointers import ( @@ -24,6 +30,8 @@ func generateSpanPointerHash(components []string) string { return hex.EncodeToString(sum[:])[:32] } +// GetSpanPointersFromS3Event calculates span pointer attributes to uniquely identify +// S3 event records. These attributes will later be used to create the _dd.span_links JSON object. func GetSpanPointersFromS3Event(event events.S3Event) []SpanPointer { var pointers []SpanPointer for _, record := range event.Records {