diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a2068324c27..2f2281b17a6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -417,6 +417,16 @@ updates: schedule: interval: "weekly" day: "sunday" + - + package-ecosystem: "gomod" + directory: "/propagators/aws" + labels: + - dependencies + - go + - "Skip Changelog" + schedule: + interval: "weekly" + day: "sunday" - package-ecosystem: "gomod" directory: "/propagators/opencensus" diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ab0d76f3a..dd91dc1b4c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - A new Amazon EKS resource detector. (#465) - A new `gcp.CloudRun` detector for detecting resource from a Cloud Run instance. (#455) +- A new AWS X-Ray ID Generator (#459) ## [0.14.0] - 2020-11-20 diff --git a/propagators/aws/go.mod b/propagators/aws/go.mod new file mode 100644 index 00000000000..d45ac185c06 --- /dev/null +++ b/propagators/aws/go.mod @@ -0,0 +1,9 @@ +module go.opentelemetry.io/contrib/propagators/aws + +go 1.15 + +require ( + github.com/stretchr/testify v1.6.1 + go.opentelemetry.io/otel v0.15.0 + go.opentelemetry.io/otel/sdk v0.15.0 +) diff --git a/propagators/aws/go.sum b/propagators/aws/go.sum new file mode 100644 index 00000000000..a39b5ea5ad0 --- /dev/null +++ b/propagators/aws/go.sum @@ -0,0 +1,22 @@ +github.com/DataDog/sketches-go v0.0.1/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +go.opentelemetry.io/otel v0.15.0 h1:CZFy2lPhxd4HlhZnYK8gRyDotksO3Ip9rBweY1vVYJw= +go.opentelemetry.io/otel v0.15.0/go.mod h1:e4GKElweB8W2gWUqbghw0B8t5MCTccc9212eNHnOHwA= +go.opentelemetry.io/otel/sdk v0.15.0 h1:Hf2dl1Ad9Hn03qjcAuAq51GP5Pv1SV5puIkS2nRhdd8= +go.opentelemetry.io/otel/sdk v0.15.0/go.mod h1:Qudkwgq81OcA9GYVlbyZ62wkLieeS1eWxIL0ufxgwoc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/propagators/aws/xray/idgenerator.go b/propagators/aws/xray/idgenerator.go new file mode 100644 index 00000000000..1cc039312f5 --- /dev/null +++ b/propagators/aws/xray/idgenerator.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package xray + +import ( + "context" + crand "crypto/rand" + "encoding/binary" + "encoding/hex" + "math/rand" + "strconv" + "sync" + "time" + + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +// IDGenerator is used for generating a new traceID and spanID +type IDGenerator struct { + sync.Mutex + randSource *rand.Rand +} + +var _ sdktrace.IDGenerator = &IDGenerator{} + +// NewSpanID returns a non-zero span ID from a randomly-chosen sequence. +func (gen *IDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { + gen.Lock() + defer gen.Unlock() + sid := trace.SpanID{} + gen.randSource.Read(sid[:]) + return sid +} + +// NewIDs returns a non-zero trace ID and a non-zero span ID. +// trace ID returned is based on AWS X-Ray TraceID format. +// - https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids +// span ID is from a randomly-chosen sequence. +func (gen *IDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { + gen.Lock() + defer gen.Unlock() + + tid := trace.TraceID{} + currentTime := getCurrentTimeHex() + copy(tid[:4], currentTime) + gen.randSource.Read(tid[4:]) + + sid := trace.SpanID{} + gen.randSource.Read(sid[:]) + return tid, sid +} + +// NewIDGenerator returns an IDGenerator reference used for sending traces to AWS X-Ray +func NewIDGenerator() *IDGenerator { + gen := &IDGenerator{} + var rngSeed int64 + _ = binary.Read(crand.Reader, binary.LittleEndian, &rngSeed) + gen.randSource = rand.New(rand.NewSource(rngSeed)) + return gen +} + +func getCurrentTimeHex() []uint8 { + currentTime := time.Now().Unix() + // Ignore error since no expected error should result from this operation + // Odd-length strings and non-hex digits are the only 2 error conditions for hex.DecodeString() + // strconv.FromatInt() do not produce odd-length strings or non-hex digits + currentTimeHex, _ := hex.DecodeString(strconv.FormatInt(currentTime, 16)) + return currentTimeHex +} diff --git a/propagators/aws/xray/idgenerator_test.go b/propagators/aws/xray/idgenerator_test.go new file mode 100644 index 00000000000..4824e510bdf --- /dev/null +++ b/propagators/aws/xray/idgenerator_test.go @@ -0,0 +1,104 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package xray + +import ( + "bytes" + "context" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/trace" +) + +func TestTraceIDIsValidLength(t *testing.T) { + idg := NewIDGenerator() + traceID, _ := idg.NewIDs(context.Background()) + + expectedTraceIDLength := 32 + assert.Equal(t, len(traceID.String()), expectedTraceIDLength, "TraceID has incorrect length.") +} + +func TestTraceIDIsUnique(t *testing.T) { + idg := NewIDGenerator() + traceID1, _ := idg.NewIDs(context.Background()) + traceID2, _ := idg.NewIDs(context.Background()) + + assert.NotEqual(t, traceID1.String(), traceID2.String(), "TraceID should be unique") +} + +func TestTraceIDTimestampInBounds(t *testing.T) { + + idg := NewIDGenerator() + + previousTime := time.Now().Unix() + + traceID, _ := idg.NewIDs(context.Background()) + + currentTime, err := strconv.ParseInt(traceID.String()[0:8], 16, 64) + require.NoError(t, err) + + nextTime := time.Now().Unix() + + assert.LessOrEqual(t, previousTime, currentTime, "TraceID is generated incorrectly with the wrong timestamp.") + assert.LessOrEqual(t, currentTime, nextTime, "TraceID is generated incorrectly with the wrong timestamp.") +} + +func TestTraceIDIsNotNil(t *testing.T) { + var nilTraceID trace.TraceID + idg := NewIDGenerator() + traceID, _ := idg.NewIDs(context.Background()) + + assert.False(t, bytes.Equal(traceID[:], nilTraceID[:]), "TraceID cannot be empty.") +} + +func TestSpanIDIsValidLength(t *testing.T) { + idg := NewIDGenerator() + ctx := context.Background() + traceID, spanID1 := idg.NewIDs(ctx) + spanID2 := idg.NewSpanID(context.Background(), traceID) + expectedSpanIDLength := 16 + + assert.Equal(t, len(spanID1.String()), expectedSpanIDLength, "SpanID has incorrect length") + assert.Equal(t, len(spanID2.String()), expectedSpanIDLength, "SpanID has incorrect length") +} + +func TestSpanIDIsUnique(t *testing.T) { + idg := NewIDGenerator() + ctx := context.Background() + traceID, spanID1 := idg.NewIDs(ctx) + _, spanID2 := idg.NewIDs(ctx) + + spanID3 := idg.NewSpanID(ctx, traceID) + spanID4 := idg.NewSpanID(ctx, traceID) + + assert.NotEqual(t, spanID1.String(), spanID2.String(), "SpanID should be unique") + assert.NotEqual(t, spanID3.String(), spanID4.String(), "SpanID should be unique") +} + +func TestSpanIDIsNotNil(t *testing.T) { + var nilSpanID trace.SpanID + idg := NewIDGenerator() + ctx := context.Background() + traceID, spanID1 := idg.NewIDs(ctx) + spanID2 := idg.NewSpanID(ctx, traceID) + + assert.False(t, bytes.Equal(spanID1[:], nilSpanID[:]), "SpanID cannot be empty.") + assert.False(t, bytes.Equal(spanID2[:], nilSpanID[:]), "SpanID cannot be empty.") +}