From f93b123cc27b185df758d79641d3b99fc57d175d Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 15:23:21 -0300 Subject: [PATCH 1/9] update ingester --- server/otlp/ingester.go | 115 ++++++++++++-------------------- server/traces/otel_converter.go | 4 +- 2 files changed, 44 insertions(+), 75 deletions(-) diff --git a/server/otlp/ingester.go b/server/otlp/ingester.go index 90a0847060..89a3af02e4 100644 --- a/server/otlp/ingester.go +++ b/server/otlp/ingester.go @@ -2,8 +2,9 @@ package otlp import ( "context" + "database/sql" + "errors" "fmt" - "strings" "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/executor" @@ -13,20 +14,29 @@ import ( "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" pb "go.opentelemetry.io/proto/otlp/collector/trace/v1" - v1 "go.opentelemetry.io/proto/otlp/trace/v1" ) +type runGetter interface { + GetRunByTraceID(context.Context, trace.TraceID) (test.Run, error) +} + +type tracePersister interface { + SaveTrace(context.Context, model.Trace) error +} + type ingester struct { - runRepository test.RunRepository - eventEmitter executor.EventEmitter - dsRepo *datastore.Repository + tracePersister tracePersister + runGetter runGetter + eventEmitter executor.EventEmitter + dsRepo *datastore.Repository } -func NewIngester(runRepository test.RunRepository, eventEmitter executor.EventEmitter, dsRepo *datastore.Repository) ingester { +func NewIngester(tracePersister tracePersister, runRepository runGetter, eventEmitter executor.EventEmitter, dsRepo *datastore.Repository) ingester { return ingester{ - runRepository: runRepository, - eventEmitter: eventEmitter, - dsRepo: dsRepo, + tracePersister: tracePersister, + runGetter: runRepository, + eventEmitter: eventEmitter, + dsRepo: dsRepo, } } @@ -42,82 +52,41 @@ func (i ingester) Ingest(ctx context.Context, request *pb.ExportTraceServiceRequ return &pb.ExportTraceServiceResponse{}, nil } - spansByTrace := i.getSpansByTrace(request) - - for traceID, spans := range spansByTrace { - i.saveSpansIntoTest(ctx, traceID, spans, requestType) - } - - return &pb.ExportTraceServiceResponse{ - PartialSuccess: &pb.ExportTracePartialSuccess{ - RejectedSpans: 0, - }, - }, nil -} - -func (i ingester) getSpansByTrace(request *pb.ExportTraceServiceRequest) map[trace.TraceID][]model.Span { - otelSpans := make([]*v1.Span, 0) - for _, resourceSpan := range request.ResourceSpans { - for _, spans := range resourceSpan.ScopeSpans { - otelSpans = append(otelSpans, spans.Spans...) - } + modelTrace := traces.FromOtelResourceSpans(request.ResourceSpans) + err = i.tracePersister.SaveTrace(ctx, modelTrace) + if err != nil { + return nil, fmt.Errorf("failed to save trace: %w", err) } - spansByTrace := make(map[trace.TraceID][]model.Span) - - for _, span := range otelSpans { - traceID := traces.CreateTraceID(span.TraceId) - var existingArray []model.Span - if spansArray, ok := spansByTrace[traceID]; ok { - existingArray = spansArray - } else { - existingArray = make([]model.Span, 0) - } - - existingArray = append(existingArray, *traces.ConvertOtelSpanIntoSpan(span)) - spansByTrace[traceID] = existingArray + err = i.notify(ctx, modelTrace, requestType) + if err != nil { + return nil, fmt.Errorf("failed to notify: %w", err) } - return spansByTrace + return &pb.ExportTraceServiceResponse{}, nil } -func (e ingester) saveSpansIntoTest(ctx context.Context, traceID trace.TraceID, spans []model.Span, requestType string) error { - run, err := e.runRepository.GetRunByTraceID(ctx, traceID) - if err != nil && strings.Contains(err.Error(), "record not found") { - // span is not part of any known test run. So it will be ignored +func (i ingester) notify(ctx context.Context, trace model.Trace, requestType string) error { + run, err := i.runGetter.GetRunByTraceID(ctx, trace.ID) + if errors.Is(err, sql.ErrNoRows) { + // trace is not part of any known test run, so no need to notify return nil } - if err != nil { - return fmt.Errorf("could not find test run with traceID %s: %w", traceID.String(), err) - } - - if run.State != test.RunStateAwaitingTrace { - // test is not waiting for trace, so we can completely ignore those as they might - // mess up with the test integrity. - // - // For example: - // Imagine that a test failed because Span A wasn't available in the trace and one minute - // later, the span is received and added to the span. When investigating the issue, - // one might be confused and maybe think it's a bug in our assertion engine - // because the assertion failed, but the span is there. However, it wasn't at - // the moment the assertion ran. - // - // So, to reduce friction and prevent long debugging hours, we can just disable this. - - return nil + // there was an actual error accessing the DB + return fmt.Errorf("error getting run by traceID: %w", err) } - existingSpans := run.Trace.Spans() - newSpans := append(existingSpans, spans...) - newTrace := model.NewTrace(traceID.String(), newSpans) - - e.eventEmitter.Emit(ctx, events.TraceOtlpServerReceivedSpans(run.TestID, run.ID, len(newSpans), requestType)) - run.Trace = &newTrace - - err = e.runRepository.UpdateRun(ctx, run) + evt := events.TraceOtlpServerReceivedSpans( + run.TestID, + run.ID, + len(trace.Flat), + requestType, + ) + err = i.eventEmitter.Emit(ctx, evt) if err != nil { - return fmt.Errorf("could not update run: %w", err) + // there was an actual error accessing the DB + return fmt.Errorf("error getting run by traceID: %w", err) } return nil diff --git a/server/traces/otel_converter.go b/server/traces/otel_converter.go index c609621804..57f1f55387 100644 --- a/server/traces/otel_converter.go +++ b/server/traces/otel_converter.go @@ -13,10 +13,10 @@ import ( ) func FromOtel(input *v1.TracesData) model.Trace { - return fromOtelResourceSpans(input.ResourceSpans) + return FromOtelResourceSpans(input.ResourceSpans) } -func fromOtelResourceSpans(resourceSpans []*v1.ResourceSpans) model.Trace { +func FromOtelResourceSpans(resourceSpans []*v1.ResourceSpans) model.Trace { flattenSpans := make([]*v1.Span, 0) for _, resource := range resourceSpans { for _, scopeSpans := range resource.ScopeSpans { From 3dd9ffd19c26d271bbb048edb4e13cbf3bd6450c Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 15:34:28 -0300 Subject: [PATCH 2/9] move model.Trace and related to traces.Trace --- server/assertions/selectors/builder.go | 4 +- server/assertions/selectors/pseudo_classes.go | 22 +- server/assertions/selectors/search.go | 18 +- server/assertions/selectors/selector.go | 18 +- server/assertions/selectors/selector_test.go | 20 +- server/executor/assertion_executor.go | 16 +- server/executor/assetion_executor_test.go | 34 +-- server/executor/default_poller_executor.go | 5 +- server/executor/outputs_processor.go | 14 +- server/executor/poller_executor_test.go | 107 +++---- .../selector_based_poller_executor_test.go | 11 +- server/executor/trigger/instrument.go | 4 +- server/expression/benchmark_test.go | 6 +- server/expression/data_store.go | 6 +- server/expression/executor_test.go | 38 +-- server/http/controller_test.go | 7 +- server/http/mappings/traces.go | 21 +- server/linter/linter.go | 6 +- server/linter/linter_test.go | 14 +- server/linter/plugins/plugins_entities.go | 4 +- .../linter/plugins/plugins_entities_test.go | 14 +- server/linter/plugins/plugins_registry.go | 4 +- server/linter/rules/enforce_dns.go | 8 +- .../linter/rules/enforce_secure_protocol.go | 6 +- .../rules/ensure_attribute_naming_rule.go | 4 +- .../linter/rules/ensure_span_naming_rule.go | 18 +- .../linter/rules/ensures_no_api_key_leak.go | 6 +- .../linter/rules/not_empty_attribute_rule.go | 4 +- .../linter/rules/required_attribute_rule.go | 4 +- .../rules/required_attribute_rule_test.go | 14 +- server/linter/rules/required_attributes.go | 24 +- server/linter/rules/rules_registry.go | 4 +- server/model/traceid.go | 11 - server/model/traces_test.go | 253 --------------- server/otlp/ingester.go | 5 +- server/test/run.go | 14 +- server/test/test_entities.go | 4 +- server/tracedb/awsxray.go | 59 ++-- server/tracedb/azureappinsights.go | 51 ++-- .../tracedb/connection/trace_polling_step.go | 3 +- server/tracedb/datasource/datasource.go | 11 +- server/tracedb/datasource/grpc.go | 3 +- server/tracedb/datasource/http.go | 3 +- server/tracedb/elasticsearchdb.go | 29 +- server/tracedb/jaegerdb.go | 12 +- server/tracedb/opensearchdb.go | 31 +- server/tracedb/otlp.go | 28 +- server/tracedb/signalfxdb.go | 25 +- server/tracedb/tempodb.go | 24 +- server/tracedb/tracedb.go | 11 +- server/traces/otel_converter.go | 41 ++- server/traces/otel_http_converter.go | 25 +- server/{model => traces}/spans.go | 2 +- server/{model => traces}/traces.go | 2 +- server/traces/traces_test.go | 288 ++++++++++++++++-- 55 files changed, 695 insertions(+), 725 deletions(-) delete mode 100644 server/model/traceid.go delete mode 100644 server/model/traces_test.go rename server/{model => traces}/spans.go (99%) rename server/{model => traces}/traces.go (99%) diff --git a/server/assertions/selectors/builder.go b/server/assertions/selectors/builder.go index 16ea5305f5..0bcdd2ba8e 100644 --- a/server/assertions/selectors/builder.go +++ b/server/assertions/selectors/builder.go @@ -5,7 +5,7 @@ import ( "github.com/alecthomas/participle/v2" "github.com/kubeshop/tracetest/server/assertions/comparator" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) var defaultParser *SelectorParser @@ -108,7 +108,7 @@ func getOperatorFunction(operator string) (FilterFunction, error) { return FilterFunction{ Name: operator, - Filter: func(span model.Span, attribute string, value Value) error { + Filter: func(span traces.Span, attribute string, value Value) error { var attrValue string if attribute == "name" { attrValue = span.Name diff --git a/server/assertions/selectors/pseudo_classes.go b/server/assertions/selectors/pseudo_classes.go index 924bae4bea..1709b4e106 100644 --- a/server/assertions/selectors/pseudo_classes.go +++ b/server/assertions/selectors/pseudo_classes.go @@ -1,10 +1,10 @@ package selectors -import "github.com/kubeshop/tracetest/server/model" +import "github.com/kubeshop/tracetest/server/traces" type PseudoClass interface { Name() string - Filter(spans []model.Span) []model.Span + Filter(spans []traces.Span) []traces.Span } type NthChildPseudoClass struct { @@ -15,12 +15,12 @@ func (nc NthChildPseudoClass) Name() string { return "nth_child" } -func (nc NthChildPseudoClass) Filter(spans []model.Span) []model.Span { +func (nc NthChildPseudoClass) Filter(spans []traces.Span) []traces.Span { if int(nc.N) < 1 || int(nc.N) > len(spans) { - return []model.Span{} + return []traces.Span{} } - return []model.Span{spans[int(nc.N-1)]} + return []traces.Span{spans[int(nc.N-1)]} } type FirstPseudoClass struct{} @@ -29,12 +29,12 @@ func (fpc FirstPseudoClass) Name() string { return "first" } -func (fpc FirstPseudoClass) Filter(spans []model.Span) []model.Span { +func (fpc FirstPseudoClass) Filter(spans []traces.Span) []traces.Span { if len(spans) == 0 { - return []model.Span{} + return []traces.Span{} } - return []model.Span{spans[0]} + return []traces.Span{spans[0]} } type LastPseudoClass struct{} @@ -43,11 +43,11 @@ func (lpc LastPseudoClass) Name() string { return "last" } -func (lpc LastPseudoClass) Filter(spans []model.Span) []model.Span { +func (lpc LastPseudoClass) Filter(spans []traces.Span) []traces.Span { length := len(spans) if length == 0 { - return []model.Span{} + return []traces.Span{} } - return []model.Span{spans[length-1]} + return []traces.Span{spans[length-1]} } diff --git a/server/assertions/selectors/search.go b/server/assertions/selectors/search.go index fc35f6cf4b..68742037ba 100644 --- a/server/assertions/selectors/search.go +++ b/server/assertions/selectors/search.go @@ -1,13 +1,13 @@ package selectors import ( - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) -func filterSpans(rootSpan model.Span, spanSelector SpanSelector) []model.Span { - filteredSpans := make([]model.Span, 0) - traverseTree(rootSpan, func(span model.Span) { +func filterSpans(rootSpan traces.Span, spanSelector SpanSelector) []traces.Span { + filteredSpans := make([]traces.Span, 0) + traverseTree(rootSpan, func(span traces.Span) { if spanSelector.MatchesFilters(span) { if spanSelector.ChildSelector != nil { childFilteredSpans := filterSpans(span, *spanSelector.ChildSelector) @@ -29,7 +29,7 @@ func filterSpans(rootSpan model.Span, spanSelector SpanSelector) []model.Span { return uniqueSpans } -func traverseTree(rootNode model.Span, fn func(model.Span)) { +func traverseTree(rootNode traces.Span, fn func(traces.Span)) { // FIX: don't use recursion to prevent stackoverflow errors on huge traces fn(rootNode) for i := range rootNode.Children { @@ -38,9 +38,9 @@ func traverseTree(rootNode model.Span, fn func(model.Span)) { } } -func filterDuplicated(spans []model.Span) []model.Span { +func filterDuplicated(spans []traces.Span) []traces.Span { existingSpans := make(map[trace.SpanID]bool, 0) - uniqueSpans := make([]model.Span, 0) + uniqueSpans := make([]traces.Span, 0) for _, span := range spans { if _, exists := existingSpans[span.ID]; !exists { uniqueSpans = append(uniqueSpans, span) @@ -51,9 +51,9 @@ func filterDuplicated(spans []model.Span) []model.Span { return uniqueSpans } -func removeSpanFromList(spans []model.Span, id trace.SpanID) []model.Span { +func removeSpanFromList(spans []traces.Span, id trace.SpanID) []traces.Span { idString := id.String() - list := make([]model.Span, 0, len(spans)) + list := make([]traces.Span, 0, len(spans)) for _, span := range spans { if span.ID.String() != idString { list = append(list, span) diff --git a/server/assertions/selectors/selector.go b/server/assertions/selectors/selector.go index f4a0843036..88bde061b9 100644 --- a/server/assertions/selectors/selector.go +++ b/server/assertions/selectors/selector.go @@ -4,8 +4,8 @@ import ( "fmt" "strconv" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/test" + "github.com/kubeshop/tracetest/server/traces" ) func FromSpanQuery(sq test.SpanQuery) Selector { @@ -17,13 +17,13 @@ type Selector struct { SpanSelectors []SpanSelector } -func (s Selector) Filter(trace model.Trace) model.Spans { +func (s Selector) Filter(trace traces.Trace) traces.Spans { if len(s.SpanSelectors) == 0 { // empty selector should select everything return getAllSpans(trace) } - allFilteredSpans := make([]model.Span, 0) + allFilteredSpans := make([]traces.Span, 0) for _, spanSelector := range s.SpanSelectors { spans := filterSpans(trace.RootSpan, spanSelector) allFilteredSpans = append(allFilteredSpans, spans...) @@ -32,9 +32,9 @@ func (s Selector) Filter(trace model.Trace) model.Spans { return allFilteredSpans } -func getAllSpans(trace model.Trace) model.Spans { - var allSpans = make(model.Spans, 0) - traverseTree(trace.RootSpan, func(span model.Span) { +func getAllSpans(trace traces.Trace) traces.Spans { + var allSpans = make(traces.Spans, 0) + traverseTree(trace.RootSpan, func(span traces.Span) { allSpans = append(allSpans, span) }) @@ -47,7 +47,7 @@ type SpanSelector struct { ChildSelector *SpanSelector } -func (ss SpanSelector) MatchesFilters(span model.Span) bool { +func (ss SpanSelector) MatchesFilters(span traces.Span) bool { for _, filter := range ss.Filters { if err := filter.Filter(span); err != nil { return false @@ -58,7 +58,7 @@ func (ss SpanSelector) MatchesFilters(span model.Span) bool { } type FilterFunction struct { - Filter func(model.Span, string, Value) error + Filter func(traces.Span, string, Value) error Name string } @@ -68,7 +68,7 @@ type filter struct { Value Value } -func (f filter) Filter(span model.Span) error { +func (f filter) Filter(span traces.Span) error { return f.Operation.Filter(span, f.Property, f.Value) } diff --git a/server/assertions/selectors/selector_test.go b/server/assertions/selectors/selector_test.go index e131abd043..aec9a83fb9 100644 --- a/server/assertions/selectors/selector_test.go +++ b/server/assertions/selectors/selector_test.go @@ -4,8 +4,8 @@ import ( "testing" "github.com/kubeshop/tracetest/server/assertions/selectors" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" @@ -18,20 +18,20 @@ var ( getPokemonFromExternalAPISpanID = gen.SpanID() updatePokemonDatabaseSpanID = gen.SpanID() ) -var pokeshopTrace = model.Trace{ +var pokeshopTrace = traces.Trace{ ID: gen.TraceID(), - RootSpan: model.Span{ + RootSpan: traces.Span{ ID: postImportSpanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "tracetest.span.type": "http", "http.status_code": "201", }, Name: "POST /import", - Children: []*model.Span{ + Children: []*traces.Span{ { ID: insertPokemonDatabaseSpanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "tracetest.span.type": "db", "db.statement": "INSERT INTO pokemon (id) values (?)", @@ -40,16 +40,16 @@ var pokeshopTrace = model.Trace{ }, { ID: getPokemonFromExternalAPISpanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop-worker", "tracetest.span.type": "http", "http.status_code": "200", }, Name: "Get pokemon from external API", - Children: []*model.Span{ + Children: []*traces.Span{ { ID: updatePokemonDatabaseSpanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop-worker", "tracetest.span.type": "db", "db.statement": "UPDATE pokemon (name = ?) WHERE id = ?", @@ -146,7 +146,7 @@ func TestSelector(t *testing.T) { } } -func ensureExpectedSpansWereReturned(t *testing.T, spanIDs []trace.SpanID, spans []model.Span) { +func ensureExpectedSpansWereReturned(t *testing.T, spanIDs []trace.SpanID, spans []traces.Span) { assert.Len(t, spans, len(spanIDs), "Should_return_the_same_number_of_spans_as_we_expected") for _, span := range spans { assert.Contains(t, spanIDs, span.ID, "span ID was returned but wasn't expected") diff --git a/server/executor/assertion_executor.go b/server/executor/assertion_executor.go index 9614e430be..00a42308ba 100644 --- a/server/executor/assertion_executor.go +++ b/server/executor/assertion_executor.go @@ -5,20 +5,20 @@ import ( "github.com/kubeshop/tracetest/server/assertions/selectors" "github.com/kubeshop/tracetest/server/expression" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/maps" "github.com/kubeshop/tracetest/server/test" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) type AssertionExecutor interface { - Assert(context.Context, test.Specs, model.Trace, []expression.DataStore) (maps.Ordered[test.SpanQuery, []test.AssertionResult], bool) + Assert(context.Context, test.Specs, traces.Trace, []expression.DataStore) (maps.Ordered[test.SpanQuery, []test.AssertionResult], bool) } type defaultAssertionExecutor struct{} -func (e defaultAssertionExecutor) Assert(_ context.Context, specs test.Specs, trace model.Trace, ds []expression.DataStore) (maps.Ordered[test.SpanQuery, []test.AssertionResult], bool) { +func (e defaultAssertionExecutor) Assert(_ context.Context, specs test.Specs, trace traces.Trace, ds []expression.DataStore) (maps.Ordered[test.SpanQuery, []test.AssertionResult], bool) { testResult := maps.Ordered[test.SpanQuery, []test.AssertionResult]{} allPassed := true for _, spec := range specs { @@ -37,7 +37,7 @@ func (e defaultAssertionExecutor) Assert(_ context.Context, specs test.Specs, tr return testResult, allPassed } -func (e defaultAssertionExecutor) assert(assertion test.Assertion, spans model.Spans, ds []expression.DataStore) test.AssertionResult { +func (e defaultAssertionExecutor) assert(assertion test.Assertion, spans traces.Spans, ds []expression.DataStore) test.AssertionResult { ds = append([]expression.DataStore{ expression.MetaAttributesDataStore{SelectedSpans: spans}, expression.VariableDataStore{}, @@ -46,7 +46,7 @@ func (e defaultAssertionExecutor) assert(assertion test.Assertion, spans model.S allPassed := true spanResults := make([]test.SpanAssertionResult, 0, len(spans)) spans. - ForEach(func(_ int, span model.Span) bool { + ForEach(func(_ int, span traces.Span) bool { res := e.assertSpan(span, ds, string(assertion)) spanResults = append(spanResults, res) @@ -57,7 +57,7 @@ func (e defaultAssertionExecutor) assert(assertion test.Assertion, spans model.S return true }). OrEmpty(func() { - res := e.assertSpan(model.Span{}, ds, string(assertion)) + res := e.assertSpan(traces.Span{}, ds, string(assertion)) spanResults = append(spanResults, res) allPassed = res.CompareErr == nil }) @@ -69,7 +69,7 @@ func (e defaultAssertionExecutor) assert(assertion test.Assertion, spans model.S } } -func (e defaultAssertionExecutor) assertSpan(span model.Span, ds []expression.DataStore, assertion string) test.SpanAssertionResult { +func (e defaultAssertionExecutor) assertSpan(span traces.Span, ds []expression.DataStore, assertion string) test.SpanAssertionResult { ds = append([]expression.DataStore{expression.AttributeDataStore{Span: span}}, ds...) expressionExecutor := expression.NewExecutor(ds...) @@ -92,7 +92,7 @@ type instrumentedAssertionExecutor struct { tracer trace.Tracer } -func (e instrumentedAssertionExecutor) Assert(ctx context.Context, defs test.Specs, trace model.Trace, ds []expression.DataStore) (maps.Ordered[test.SpanQuery, []test.AssertionResult], bool) { +func (e instrumentedAssertionExecutor) Assert(ctx context.Context, defs test.Specs, trace traces.Trace, ds []expression.DataStore) (maps.Ordered[test.SpanQuery, []test.AssertionResult], bool) { ctx, span := e.tracer.Start(ctx, "Execute assertions") defer span.End() diff --git a/server/executor/assetion_executor_test.go b/server/executor/assetion_executor_test.go index 271a94314c..db6d23c954 100644 --- a/server/executor/assetion_executor_test.go +++ b/server/executor/assetion_executor_test.go @@ -7,10 +7,10 @@ import ( "github.com/kubeshop/tracetest/server/assertions/comparator" "github.com/kubeshop/tracetest/server/executor" "github.com/kubeshop/tracetest/server/expression" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" "github.com/kubeshop/tracetest/server/pkg/maps" "github.com/kubeshop/tracetest/server/test" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" @@ -22,7 +22,7 @@ func TestAssertion(t *testing.T) { cases := []struct { name string testDef test.Specs - trace model.Trace + trace traces.Trace expectedResult maps.Ordered[test.SpanQuery, []test.AssertionResult] expectedAllPassed bool }{ @@ -36,10 +36,10 @@ func TestAssertion(t *testing.T) { }, }, }, - trace: model.Trace{ - RootSpan: model.Span{ + trace: traces.Trace{ + RootSpan: traces.Span{ ID: spanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "tracetest.span.duration": "2000", }, @@ -75,10 +75,10 @@ func TestAssertion(t *testing.T) { }, }, }, - trace: model.Trace{ - RootSpan: model.Span{ + trace: traces.Trace{ + RootSpan: traces.Span{ ID: spanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "tracetest.span.duration": "2000", }, @@ -121,10 +121,10 @@ func TestAssertion(t *testing.T) { }, }, }, - trace: model.Trace{ - RootSpan: model.Span{ + trace: traces.Trace{ + RootSpan: traces.Span{ ID: spanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "http.response.body": `{"id":52}`, "tracetest.span.duration": "21000000", @@ -166,10 +166,10 @@ func TestAssertion(t *testing.T) { }, }, }, - trace: model.Trace{ - RootSpan: model.Span{ + trace: traces.Trace{ + RootSpan: traces.Span{ ID: spanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "http.response.body": `{"id":52}`, "tracetest.span.duration": "25187564", // 25ms @@ -201,10 +201,10 @@ func TestAssertion(t *testing.T) { }, }, }, - trace: model.Trace{ - RootSpan: model.Span{ + trace: traces.Trace{ + RootSpan: traces.Span{ ID: spanID, - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": "Pokeshop", "http.response.body": `{"id":52}`, "tracetest.span.duration": "35000000", // 35ms diff --git a/server/executor/default_poller_executor.go b/server/executor/default_poller_executor.go index a9532221ff..66937cffc8 100644 --- a/server/executor/default_poller_executor.go +++ b/server/executor/default_poller_executor.go @@ -11,6 +11,7 @@ import ( "github.com/kubeshop/tracetest/server/resourcemanager" "github.com/kubeshop/tracetest/server/test" "github.com/kubeshop/tracetest/server/tracedb" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) @@ -144,7 +145,7 @@ func (pe DefaultPollerExecutor) ExecuteRequest(ctx context.Context, job *Job) (P newRoot := test.NewTracetestRootSpan(job.Run) job.Run.Trace = job.Run.Trace.InsertRootSpan(newRoot) } else { - job.Run.Trace.RootSpan = model.AugmentRootSpan(job.Run.Trace.RootSpan, job.Run.TriggerResult) + job.Run.Trace.RootSpan = traces.AugmentRootSpan(job.Run.Trace.RootSpan, job.Run.TriggerResult) } job.Run = job.Run.SuccessfullyPolledTraces(job.Run.Trace) @@ -196,7 +197,7 @@ func (pe DefaultPollerExecutor) testConnection(ctx context.Context, traceDB trac return nil } -func (pe DefaultPollerExecutor) donePollingTraces(job *Job, traceDB tracedb.TraceDB, trace model.Trace) (bool, string) { +func (pe DefaultPollerExecutor) donePollingTraces(job *Job, traceDB tracedb.TraceDB, trace traces.Trace) (bool, string) { if !traceDB.ShouldRetry() { return true, "TraceDB is not retryable" } diff --git a/server/executor/outputs_processor.go b/server/executor/outputs_processor.go index 4bba7f9223..ecc9d3e682 100644 --- a/server/executor/outputs_processor.go +++ b/server/executor/outputs_processor.go @@ -7,15 +7,15 @@ import ( "github.com/kubeshop/tracetest/server/assertions/selectors" "github.com/kubeshop/tracetest/server/expression" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/maps" "github.com/kubeshop/tracetest/server/test" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) -type OutputsProcessorFn func(context.Context, test.Outputs, model.Trace, []expression.DataStore) (maps.Ordered[string, test.RunOutput], error) +type OutputsProcessorFn func(context.Context, test.Outputs, traces.Trace, []expression.DataStore) (maps.Ordered[string, test.RunOutput], error) func InstrumentedOutputProcessor(tracer trace.Tracer) OutputsProcessorFn { op := instrumentedOutputProcessor{tracer} @@ -26,7 +26,7 @@ type instrumentedOutputProcessor struct { tracer trace.Tracer } -func (op instrumentedOutputProcessor) process(ctx context.Context, outputs test.Outputs, t model.Trace, ds []expression.DataStore) (maps.Ordered[string, test.RunOutput], error) { +func (op instrumentedOutputProcessor) process(ctx context.Context, outputs test.Outputs, t traces.Trace, ds []expression.DataStore) (maps.Ordered[string, test.RunOutput], error) { ctx, span := op.tracer.Start(ctx, "Process outputs") defer span.End() @@ -51,7 +51,7 @@ func (op instrumentedOutputProcessor) process(ctx context.Context, outputs test. return result, err } -func outputProcessor(ctx context.Context, outputs test.Outputs, tr model.Trace, ds []expression.DataStore) (maps.Ordered[string, test.RunOutput], error) { +func outputProcessor(ctx context.Context, outputs test.Outputs, tr traces.Trace, ds []expression.DataStore) (maps.Ordered[string, test.RunOutput], error) { res := maps.Ordered[string, test.RunOutput]{} parsed, err := parseOutputs(outputs) @@ -84,7 +84,7 @@ func outputProcessor(ctx context.Context, outputs test.Outputs, tr model.Trace, resolved := false var outputError error = nil spans. - ForEach(func(_ int, span model.Span) bool { + ForEach(func(_ int, span traces.Span) bool { value = extractAttr(span, stores, out.expr) spanId = span.ID.String() resolved = true @@ -92,7 +92,7 @@ func outputProcessor(ctx context.Context, outputs test.Outputs, tr model.Trace, return false }). OrEmpty(func() { - value = extractAttr(model.Span{}, stores, out.expr) + value = extractAttr(traces.Span{}, stores, out.expr) resolved = false outputError = fmt.Errorf(`cannot find matching spans for output "%s"`, key) }) @@ -118,7 +118,7 @@ func outputProcessor(ctx context.Context, outputs test.Outputs, tr model.Trace, return res, nil } -func extractAttr(span model.Span, ds []expression.DataStore, expr expression.Expr) string { +func extractAttr(span traces.Span, ds []expression.DataStore, expr expression.Expr) string { ds = append([]expression.DataStore{expression.AttributeDataStore{Span: span}}, ds...) expressionExecutor := expression.NewExecutor(ds...) diff --git a/server/executor/poller_executor_test.go b/server/executor/poller_executor_test.go index 6b00270643..0fdc203987 100644 --- a/server/executor/poller_executor_test.go +++ b/server/executor/poller_executor_test.go @@ -17,6 +17,7 @@ import ( "github.com/kubeshop/tracetest/server/testdb" "github.com/kubeshop/tracetest/server/tracedb" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" "github.com/kubeshop/tracetest/server/tracing" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -44,7 +45,7 @@ func Test_PollerExecutor_ExecuteRequest_NoRootSpan_NoSpanCase(t *testing.T) { retryDelay := 1 * time.Second maxWaitTimeForTrace := 30 * time.Second - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, {}, } @@ -81,7 +82,7 @@ func Test_PollerExecutor_ExecuteRequest_NoRootSpan_OneSpanCase(t *testing.T) { retryDelay := 1 * time.Second maxWaitTimeForTrace := 30 * time.Second - trace := model.NewTrace(randomIDGenerator.TraceID().String(), []model.Span{ + trace := traces.NewTrace(randomIDGenerator.TraceID().String(), []traces.Span{ { ID: randomIDGenerator.SpanID(), Name: "HTTP API", @@ -90,12 +91,12 @@ func Test_PollerExecutor_ExecuteRequest_NoRootSpan_OneSpanCase(t *testing.T) { Attributes: map[string]string{ "testSpan": "true", }, - Children: []*model.Span{}, + Children: []*traces.Span{}, }, }) // test - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, trace, trace, @@ -133,7 +134,7 @@ func Test_PollerExecutor_ExecuteRequest_NoRootSpan_TwoSpansCase(t *testing.T) { traceID := randomIDGenerator.TraceID().String() - firstSpan := model.Span{ + firstSpan := traces.Span{ ID: randomIDGenerator.SpanID(), Name: "HTTP API", StartTime: time.Now(), @@ -141,26 +142,26 @@ func Test_PollerExecutor_ExecuteRequest_NoRootSpan_TwoSpansCase(t *testing.T) { Attributes: map[string]string{ "testSpan": "true", }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - secondSpan := model.Span{ + secondSpan := traces.Span{ ID: randomIDGenerator.SpanID(), Name: "Database query", StartTime: firstSpan.EndTime, EndTime: firstSpan.EndTime.Add(retryDelay), Attributes: map[string]string{ - "testSpan": "true", - model.TracetestMetadataFieldParentID: firstSpan.ID.String(), + "testSpan": "true", + traces.TracetestMetadataFieldParentID: firstSpan.ID.String(), }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - traceWithOneSpan := model.NewTrace(traceID, []model.Span{firstSpan}) - traceWithTwoSpans := model.NewTrace(traceID, []model.Span{firstSpan, secondSpan}) + traceWithOneSpan := traces.NewTrace(traceID, []traces.Span{firstSpan}) + traceWithTwoSpans := traces.NewTrace(traceID, []traces.Span{firstSpan, secondSpan}) // test - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, traceWithOneSpan, traceWithTwoSpans, @@ -196,20 +197,20 @@ func Test_PollerExecutor_ExecuteRequest_WithRootSpan_NoSpanCase(t *testing.T) { retryDelay := 1 * time.Second maxWaitTimeForTrace := 3 * time.Second - rootSpan := model.Span{ + rootSpan := traces.Span{ ID: randomIDGenerator.SpanID(), - Name: model.TriggerSpanName, + Name: traces.TriggerSpanName, StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ "testSpan": "true", }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - trace := model.NewTrace(randomIDGenerator.TraceID().String(), []model.Span{rootSpan}) + trace := traces.NewTrace(randomIDGenerator.TraceID().String(), []traces.Span{rootSpan}) - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, trace, trace, @@ -249,16 +250,16 @@ func Test_PollerExecutor_ExecuteRequest_WithRootSpan_OneSpanCase(t *testing.T) { rootSpanID := randomIDGenerator.SpanID() - trace := model.NewTrace(randomIDGenerator.TraceID().String(), []model.Span{ + trace := traces.NewTrace(randomIDGenerator.TraceID().String(), []traces.Span{ { ID: rootSpanID, - Name: model.TriggerSpanName, + Name: traces.TriggerSpanName, StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ "testSpan": "true", }, - Children: []*model.Span{}, + Children: []*traces.Span{}, }, { ID: randomIDGenerator.SpanID(), @@ -266,15 +267,15 @@ func Test_PollerExecutor_ExecuteRequest_WithRootSpan_OneSpanCase(t *testing.T) { StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ - "testSpan": "true", - model.TracetestMetadataFieldParentID: rootSpanID.String(), + "testSpan": "true", + traces.TracetestMetadataFieldParentID: rootSpanID.String(), }, - Children: []*model.Span{}, + Children: []*traces.Span{}, }, }) // test - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, trace, trace, @@ -310,34 +311,34 @@ func Test_PollerExecutor_ExecuteRequest_WithRootSpan_OneDelayedSpanCase(t *testi retryDelay := 1 * time.Second maxWaitTimeForTrace := 30 * time.Second - rootSpan := model.Span{ + rootSpan := traces.Span{ ID: randomIDGenerator.SpanID(), - Name: model.TriggerSpanName, + Name: traces.TriggerSpanName, StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ "testSpan": "true", }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - apiSpan := model.Span{ + apiSpan := traces.Span{ ID: randomIDGenerator.SpanID(), Name: "HTTP API", StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ - "testSpan": "true", - model.TracetestMetadataFieldParentID: rootSpan.ID.String(), + "testSpan": "true", + traces.TracetestMetadataFieldParentID: rootSpan.ID.String(), }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - traceWithOnlyRoot := model.NewTrace(randomIDGenerator.TraceID().String(), []model.Span{rootSpan}) - completeTrace := model.NewTrace(randomIDGenerator.TraceID().String(), []model.Span{rootSpan, apiSpan}) + traceWithOnlyRoot := traces.NewTrace(randomIDGenerator.TraceID().String(), []traces.Span{rootSpan}) + completeTrace := traces.NewTrace(randomIDGenerator.TraceID().String(), []traces.Span{rootSpan, apiSpan}) // test - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, traceWithOnlyRoot, traceWithOnlyRoot, @@ -379,46 +380,46 @@ func Test_PollerExecutor_ExecuteRequest_WithRootSpan_TwoSpansCase(t *testing.T) traceID := randomIDGenerator.TraceID().String() - rootSpan := model.Span{ + rootSpan := traces.Span{ ID: randomIDGenerator.SpanID(), - Name: model.TriggerSpanName, + Name: traces.TriggerSpanName, StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ "testSpan": "true", }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - firstSpan := model.Span{ + firstSpan := traces.Span{ ID: randomIDGenerator.SpanID(), Name: "HTTP API", StartTime: time.Now(), EndTime: time.Now().Add(retryDelay), Attributes: map[string]string{ - "testSpan": "true", - model.TracetestMetadataFieldParentID: rootSpan.ID.String(), + "testSpan": "true", + traces.TracetestMetadataFieldParentID: rootSpan.ID.String(), }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - secondSpan := model.Span{ + secondSpan := traces.Span{ ID: randomIDGenerator.SpanID(), Name: "Database query", StartTime: firstSpan.EndTime, EndTime: firstSpan.EndTime.Add(retryDelay), Attributes: map[string]string{ - "testSpan": "true", - model.TracetestMetadataFieldParentID: firstSpan.ID.String(), + "testSpan": "true", + traces.TracetestMetadataFieldParentID: firstSpan.ID.String(), }, - Children: []*model.Span{}, + Children: []*traces.Span{}, } - traceWithOneSpan := model.NewTrace(traceID, []model.Span{rootSpan, firstSpan}) - traceWithTwoSpans := model.NewTrace(traceID, []model.Span{rootSpan, firstSpan, secondSpan}) + traceWithOneSpan := traces.NewTrace(traceID, []traces.Span{rootSpan, firstSpan}) + traceWithTwoSpans := traces.NewTrace(traceID, []traces.Span{rootSpan, firstSpan, secondSpan}) // test - tracePerIteration := []model.Trace{ + tracePerIteration := []traces.Trace{ {}, traceWithOneSpan, traceWithTwoSpans, @@ -505,7 +506,7 @@ func executeAndValidatePollingRequests(t *testing.T, retryDelay, maxWaitTimeForT } } -func getPollerExecutorWithMocks(t *testing.T, tracePerIteration []model.Trace) *executor.InstrumentedPollerExecutor { +func getPollerExecutorWithMocks(t *testing.T, tracePerIteration []traces.Trace) *executor.InstrumentedPollerExecutor { updater := getRunUpdaterMock(t) tracer := getTracerMock(t) testDB := getRunRepositoryMock(t) @@ -583,11 +584,11 @@ func getTracerMock(t *testing.T) trace.Tracer { // TraceDB type traceDBMock struct { - tracePerIteration []model.Trace + tracePerIteration []traces.Trace state *traceDBState } -func (db *traceDBMock) GetTraceByID(_ context.Context, _ string) (t model.Trace, err error) { +func (db *traceDBMock) GetTraceByID(_ context.Context, _ string) (t traces.Trace, err error) { trace := db.tracePerIteration[db.state.currentIteration] db.state.currentIteration += 1 @@ -619,7 +620,7 @@ type traceDBState struct { currentIteration int } -func getTraceDBMockFactory(t *testing.T, tracePerIteration []model.Trace, state *traceDBState) func(datastore.DataStore) (tracedb.TraceDB, error) { +func getTraceDBMockFactory(t *testing.T, tracePerIteration []traces.Trace, state *traceDBState) func(datastore.DataStore) (tracedb.TraceDB, error) { t.Helper() return func(ds datastore.DataStore) (tracedb.TraceDB, error) { diff --git a/server/executor/selector_based_poller_executor_test.go b/server/executor/selector_based_poller_executor_test.go index d287978773..1454b10488 100644 --- a/server/executor/selector_based_poller_executor_test.go +++ b/server/executor/selector_based_poller_executor_test.go @@ -8,6 +8,7 @@ import ( "github.com/kubeshop/tracetest/server/executor/pollingprofile" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/test" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -72,7 +73,7 @@ func TestSelectorBasedPollerExecutor(t *testing.T) { } testObj := test.Test{Specs: specs} - trace := model.NewTrace(randomIDGenerator.TraceID().String(), make([]model.Span, 0)) + trace := traces.NewTrace(randomIDGenerator.TraceID().String(), make([]traces.Span, 0)) run := test.Run{Trace: &trace} request := createRequest(testObj, run) @@ -94,7 +95,7 @@ func TestSelectorBasedPollerExecutor(t *testing.T) { } testObj := test.Test{Specs: specs} - trace := model.NewTrace(randomIDGenerator.TraceID().String(), make([]model.Span, 0)) + trace := traces.NewTrace(randomIDGenerator.TraceID().String(), make([]traces.Span, 0)) run := test.Run{Trace: &trace} request := createRequest(testObj, run) @@ -132,10 +133,10 @@ func TestSelectorBasedPollerExecutor(t *testing.T) { } testObj := test.Test{Specs: specs} - rootSpan := model.Span{ID: randomIDGenerator.SpanID(), Name: "Tracetest trigger", Attributes: make(model.Attributes)} - trace := model.NewTrace(randomIDGenerator.TraceID().String(), []model.Span{ + rootSpan := traces.Span{ID: randomIDGenerator.SpanID(), Name: "Tracetest trigger", Attributes: make(traces.Attributes)} + trace := traces.NewTrace(randomIDGenerator.TraceID().String(), []traces.Span{ rootSpan, - {ID: randomIDGenerator.SpanID(), Name: "GET /api/tests", Attributes: model.Attributes{model.TracetestMetadataFieldParentID: rootSpan.ID.String()}}, + {ID: randomIDGenerator.SpanID(), Name: "GET /api/tests", Attributes: traces.Attributes{traces.TracetestMetadataFieldParentID: rootSpan.ID.String()}}, }) run := test.Run{Trace: &trace} diff --git a/server/executor/trigger/instrument.go b/server/executor/trigger/instrument.go index 9a4342512a..f7b4247266 100644 --- a/server/executor/trigger/instrument.go +++ b/server/executor/trigger/instrument.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/test" "github.com/kubeshop/tracetest/server/test/trigger" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/contrib/propagators/aws/xray" "go.opentelemetry.io/contrib/propagators/b3" "go.opentelemetry.io/contrib/propagators/jaeger" @@ -59,7 +59,7 @@ func (t *instrumentedTriggerer) Trigger(ctx context.Context, test test.Test, opt triggerCtx := trace.ContextWithSpanContext(context.Background(), spanContext) - triggerSpanCtx, triggerSpan := t.triggerSpanTracer.Start(triggerCtx, model.TriggerSpanName) + triggerSpanCtx, triggerSpan := t.triggerSpanTracer.Start(triggerCtx, traces.TriggerSpanName) defer triggerSpan.End() triggerSpan.SpanContext().TraceState().Insert("tracetest", "true") diff --git a/server/expression/benchmark_test.go b/server/expression/benchmark_test.go index 424b234134..308548d8c2 100644 --- a/server/expression/benchmark_test.go +++ b/server/expression/benchmark_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/kubeshop/tracetest/server/expression" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) func BenchmarkSimpleExpressions(b *testing.B) { @@ -21,8 +21,8 @@ func BenchmarkJSONPathExpressions(b *testing.B) { statement := `attr:my_json | json_path '[*].id' | count = 3` attributeDataStore := expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "my_json": getJSON(), }, }, diff --git a/server/expression/data_store.go b/server/expression/data_store.go index 89dc5e55bd..52d3d8ce4f 100644 --- a/server/expression/data_store.go +++ b/server/expression/data_store.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" "github.com/kubeshop/tracetest/server/variableset" ) @@ -18,7 +18,7 @@ var attributeAlias = map[string]string{ } type AttributeDataStore struct { - Span model.Span + Span traces.Span } func (ds AttributeDataStore) Source() string { @@ -50,7 +50,7 @@ func (ds AttributeDataStore) Get(name string) (string, error) { } type MetaAttributesDataStore struct { - SelectedSpans []model.Span + SelectedSpans []traces.Span } func (ds MetaAttributesDataStore) Source() string { diff --git a/server/expression/executor_test.go b/server/expression/executor_test.go index d3c5535e06..eb230d43d0 100644 --- a/server/expression/executor_test.go +++ b/server/expression/executor_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/kubeshop/tracetest/server/expression" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" "github.com/kubeshop/tracetest/server/variableset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -101,8 +101,8 @@ func TestAttributeExecution(t *testing.T) { ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "my_attribute": "42", }, }, @@ -114,8 +114,8 @@ func TestAttributeExecution(t *testing.T) { ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "dapr-app-id": "42", }, }, @@ -133,8 +133,8 @@ func TestStringInterpolationExecution(t *testing.T) { Query: `attr:text = 'this run took ${"25ms"}'`, ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "text": "this run took 25ms", }, }, @@ -157,8 +157,8 @@ func TestFilterExecution(t *testing.T) { Query: `attr:tracetest.response.body | json_path '.id' = 8`, ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "tracetest.response.body": `{"id": 8, "name": "john doe"}`, }, }, @@ -197,7 +197,7 @@ func TestMetaAttributesExecution(t *testing.T) { ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{}, MetaAttributesDataStore: expression.MetaAttributesDataStore{ - SelectedSpans: []model.Span{ + SelectedSpans: []traces.Span{ // We don't have to fill the spans details to make the meta attribute work {}, {}, @@ -211,7 +211,7 @@ func TestMetaAttributesExecution(t *testing.T) { ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{}, MetaAttributesDataStore: expression.MetaAttributesDataStore{ - SelectedSpans: []model.Span{ + SelectedSpans: []traces.Span{ {}, {}, }, @@ -354,8 +354,8 @@ func TestResolveStatementAttributeExecution(t *testing.T) { ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "my_attribute": "42", }, }, @@ -373,8 +373,8 @@ func TestResolveStatementStringInterpolationExecution(t *testing.T) { Query: `'this run took ${"25ms"}'`, ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "text": "this run took 25ms", }, }, @@ -397,8 +397,8 @@ func TestResolveStatementFilterExecution(t *testing.T) { Query: `attr:tracetest.response.body`, ShouldPass: true, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "tracetest.response.body": `{"id": 8, "name": "john doe"}`, }, }, @@ -448,8 +448,8 @@ func TestFailureCases(t *testing.T) { ExpectedErrorMessage: `resolution error: attribute "my_attribute" not found`, AttributeDataStore: expression.AttributeDataStore{ - Span: model.Span{ - Attributes: model.Attributes{ + Span: traces.Span{ + Attributes: traces.Attributes{ "attr1": "1", "attr2": "2", }, diff --git a/server/http/controller_test.go b/server/http/controller_test.go index 7d225fdda6..2126f16dee 100644 --- a/server/http/controller_test.go +++ b/server/http/controller_test.go @@ -7,7 +7,6 @@ import ( "github.com/kubeshop/tracetest/server/assertions/comparator" "github.com/kubeshop/tracetest/server/http" "github.com/kubeshop/tracetest/server/http/mappings" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/openapi" "github.com/kubeshop/tracetest/server/pkg/id" "github.com/kubeshop/tracetest/server/test" @@ -25,12 +24,12 @@ var ( ID: 1, TestID: id.ID("abc123"), TraceID: id.NewRandGenerator().TraceID(), - Trace: &model.Trace{ + Trace: &traces.Trace{ ID: id.NewRandGenerator().TraceID(), - RootSpan: model.Span{ + RootSpan: traces.Span{ ID: id.NewRandGenerator().SpanID(), Name: "POST /pokemon/import", - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "tracetest.span.type": "http", "service.name": "pokeshop", "http.response.body": `{"id":52}`, diff --git a/server/http/mappings/traces.go b/server/http/mappings/traces.go index eb8a7a1f23..63124fc29d 100644 --- a/server/http/mappings/traces.go +++ b/server/http/mappings/traces.go @@ -4,7 +4,6 @@ import ( "strconv" "time" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/openapi" "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" @@ -12,7 +11,7 @@ import ( // out -func (m OpenAPI) Trace(in *model.Trace) openapi.Trace { +func (m OpenAPI) Trace(in *traces.Trace) openapi.Trace { if in == nil { return openapi.Trace{} } @@ -29,7 +28,7 @@ func (m OpenAPI) Trace(in *model.Trace) openapi.Trace { } } -func (m OpenAPI) Span(in model.Span) openapi.Span { +func (m OpenAPI) Span(in traces.Span) openapi.Span { parentID := "" if in.Parent != nil { parentID = in.Parent.ID.String() @@ -46,7 +45,7 @@ func (m OpenAPI) Span(in model.Span) openapi.Span { kind := string(in.Kind) if kind == "" { - kind = string(model.SpanKindUnespecified) + kind = string(traces.SpanKindUnespecified) } return openapi.Span{ @@ -61,7 +60,7 @@ func (m OpenAPI) Span(in model.Span) openapi.Span { } } -func (m OpenAPI) Spans(in []*model.Span) []openapi.Span { +func (m OpenAPI) Spans(in []*traces.Span) []openapi.Span { spans := make([]openapi.Span, len(in)) for i, s := range in { spans[i] = m.Span(*s) @@ -72,17 +71,17 @@ func (m OpenAPI) Spans(in []*model.Span) []openapi.Span { // in -func (m Model) Trace(in openapi.Trace) *model.Trace { +func (m Model) Trace(in openapi.Trace) *traces.Trace { tid, _ := trace.TraceIDFromHex(in.TraceId) - return &model.Trace{ + return &traces.Trace{ ID: tid, RootSpan: m.Span(in.Tree, nil), } } -func (m Model) Span(in openapi.Span, parent *model.Span) model.Span { +func (m Model) Span(in openapi.Span, parent *traces.Span) traces.Span { sid, _ := trace.SpanIDFromHex(in.Id) - span := model.Span{ + span := traces.Span{ ID: sid, Attributes: in.Attributes, Name: in.Name, @@ -95,8 +94,8 @@ func (m Model) Span(in openapi.Span, parent *model.Span) model.Span { return span } -func (m Model) Spans(in []openapi.Span, parent *model.Span) []*model.Span { - spans := make([]*model.Span, len(in)) +func (m Model) Spans(in []openapi.Span, parent *traces.Span) []*traces.Span { + spans := make([]*traces.Span, len(in)) for i, s := range in { span := m.Span(s, parent) spans[i] = &span diff --git a/server/linter/linter.go b/server/linter/linter.go index 6c2282ea19..f4567752be 100644 --- a/server/linter/linter.go +++ b/server/linter/linter.go @@ -6,7 +6,7 @@ import ( "github.com/kubeshop/tracetest/server/linter/analyzer" "github.com/kubeshop/tracetest/server/linter/plugins" "github.com/kubeshop/tracetest/server/linter/rules" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) var ( @@ -39,7 +39,7 @@ var ( ) type Linter interface { - Run(context.Context, model.Trace, analyzer.Linter) (analyzer.LinterResult, error) + Run(context.Context, traces.Trace, analyzer.Linter) (analyzer.LinterResult, error) } type linter struct { @@ -54,7 +54,7 @@ func NewLinter(registry *plugins.Registry) Linter { var _ Linter = &linter{} -func (l linter) Run(ctx context.Context, trace model.Trace, config analyzer.Linter) (analyzer.LinterResult, error) { +func (l linter) Run(ctx context.Context, trace traces.Trace, config analyzer.Linter) (analyzer.LinterResult, error) { cfgPlugins := config.EnabledPlugins() pluginResults := make([]analyzer.PluginResult, len(cfgPlugins)) diff --git a/server/linter/linter_test.go b/server/linter/linter_test.go index a59d973c51..9a4312dc78 100644 --- a/server/linter/linter_test.go +++ b/server/linter/linter_test.go @@ -7,8 +7,8 @@ import ( "github.com/kubeshop/tracetest/server/linter" "github.com/kubeshop/tracetest/server/linter/analyzer" "github.com/kubeshop/tracetest/server/linter/plugins" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) @@ -70,9 +70,9 @@ func TestLinter(t *testing.T) { }) } -func spanWithAttributes(spanType string, attributes map[string]string) model.Span { - span := model.Span{ - Attributes: make(model.Attributes, 0), +func spanWithAttributes(spanType string, attributes map[string]string) traces.Span { + span := traces.Span{ + Attributes: make(traces.Attributes, 0), } for name, value := range attributes { @@ -84,9 +84,9 @@ func spanWithAttributes(spanType string, attributes map[string]string) model.Spa return span } -func traceWithSpans(spans ...model.Span) model.Trace { - trace := model.Trace{ - Flat: make(map[trace.SpanID]*model.Span, 0), +func traceWithSpans(spans ...traces.Span) traces.Trace { + trace := traces.Trace{ + Flat: make(map[trace.SpanID]*traces.Span, 0), } for _, span := range spans { diff --git a/server/linter/plugins/plugins_entities.go b/server/linter/plugins/plugins_entities.go index 7b15ce0f3c..7f7c26d8f5 100644 --- a/server/linter/plugins/plugins_entities.go +++ b/server/linter/plugins/plugins_entities.go @@ -5,7 +5,7 @@ import ( "github.com/kubeshop/tracetest/server/linter/analyzer" "github.com/kubeshop/tracetest/server/linter/rules" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type BasePlugin struct { @@ -25,7 +25,7 @@ func (p BasePlugin) RuleRegistry() *rules.RuleRegistry { return p.ruleRegistry } -func (p BasePlugin) Execute(ctx context.Context, trace model.Trace, config analyzer.LinterPlugin) (analyzer.PluginResult, error) { +func (p BasePlugin) Execute(ctx context.Context, trace traces.Trace, config analyzer.LinterPlugin) (analyzer.PluginResult, error) { res := make([]analyzer.RuleResult, 0, len(config.Rules)) for _, cfgRule := range config.Rules { diff --git a/server/linter/plugins/plugins_entities_test.go b/server/linter/plugins/plugins_entities_test.go index b9e7ed18fa..0cf1c2b409 100644 --- a/server/linter/plugins/plugins_entities_test.go +++ b/server/linter/plugins/plugins_entities_test.go @@ -7,8 +7,8 @@ import ( "github.com/kubeshop/tracetest/server/linter/analyzer" "github.com/kubeshop/tracetest/server/linter/plugins" "github.com/kubeshop/tracetest/server/linter/rules" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) @@ -64,9 +64,9 @@ func TestAnalyzerEntities(t *testing.T) { }) } -func spanWithAttributes(spanType string, attributes map[string]string) model.Span { - span := model.Span{ - Attributes: make(model.Attributes, 0), +func spanWithAttributes(spanType string, attributes map[string]string) traces.Span { + span := traces.Span{ + Attributes: make(traces.Attributes, 0), } for name, value := range attributes { @@ -78,9 +78,9 @@ func spanWithAttributes(spanType string, attributes map[string]string) model.Spa return span } -func traceWithSpans(spans ...model.Span) model.Trace { - trace := model.Trace{ - Flat: make(map[trace.SpanID]*model.Span, 0), +func traceWithSpans(spans ...traces.Span) traces.Trace { + trace := traces.Trace{ + Flat: make(map[trace.SpanID]*traces.Span, 0), } for _, span := range spans { diff --git a/server/linter/plugins/plugins_registry.go b/server/linter/plugins/plugins_registry.go index 4f148eaae1..f15952967c 100644 --- a/server/linter/plugins/plugins_registry.go +++ b/server/linter/plugins/plugins_registry.go @@ -6,11 +6,11 @@ import ( "github.com/kubeshop/tracetest/server/linter/analyzer" "github.com/kubeshop/tracetest/server/linter/rules" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type Plugin interface { - Execute(context.Context, model.Trace, analyzer.LinterPlugin) (analyzer.PluginResult, error) + Execute(context.Context, traces.Trace, analyzer.LinterPlugin) (analyzer.PluginResult, error) ID() string RuleRegistry() *rules.RuleRegistry } diff --git a/server/linter/rules/enforce_dns.go b/server/linter/rules/enforce_dns.go index c7341dd778..343c6915bc 100644 --- a/server/linter/rules/enforce_dns.go +++ b/server/linter/rules/enforce_dns.go @@ -6,7 +6,7 @@ import ( "regexp" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type ensuresDnsUsage struct{} @@ -24,7 +24,7 @@ func (r ensuresDnsUsage) ID() string { return analyzer.EnforceDnsRuleID } -func (r ensuresDnsUsage) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r ensuresDnsUsage) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { passed := true res := make([]analyzer.Result, 0) @@ -41,7 +41,7 @@ func (r ensuresDnsUsage) Evaluate(ctx context.Context, trace model.Trace, config return analyzer.NewRuleResult(config, analyzer.EvalRuleResult{Passed: passed, Results: res}), nil } -func (r ensuresDnsUsage) validate(span *model.Span) analyzer.Result { +func (r ensuresDnsUsage) validate(span *traces.Span) analyzer.Result { ipFields := make([]analyzer.Error, 0) ipRegexp := regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`) @@ -55,7 +55,7 @@ func (r ensuresDnsUsage) validate(span *model.Span) analyzer.Result { } for _, field := range clientDnsFields { - if span.Kind == model.SpanKindClient && span.Attributes.Get(field) != "" && ipRegexp.MatchString(span.Attributes.Get(field)) { + if span.Kind == traces.SpanKindClient && span.Attributes.Get(field) != "" && ipRegexp.MatchString(span.Attributes.Get(field)) { ipFields = append(ipFields, analyzer.Error{ Value: field, Description: fmt.Sprintf("Usage of a IP endpoint instead of DNS found for attribute: %s. Value: %s", field, span.Attributes.Get(field)), diff --git a/server/linter/rules/enforce_secure_protocol.go b/server/linter/rules/enforce_secure_protocol.go index 90501ba9af..8a247a974c 100644 --- a/server/linter/rules/enforce_secure_protocol.go +++ b/server/linter/rules/enforce_secure_protocol.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type enforceHttpsProtocolRule struct{} @@ -23,7 +23,7 @@ func (r enforceHttpsProtocolRule) ID() string { return analyzer.EnforceHttpsProtocolRuleID } -func (r enforceHttpsProtocolRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r enforceHttpsProtocolRule) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { passed := true res := make([]analyzer.Result, 0) @@ -42,7 +42,7 @@ func (r enforceHttpsProtocolRule) Evaluate(ctx context.Context, trace model.Trac return analyzer.NewRuleResult(config, analyzer.EvalRuleResult{Passed: passed, Results: res}), nil } -func (r enforceHttpsProtocolRule) validate(span *model.Span) analyzer.Result { +func (r enforceHttpsProtocolRule) validate(span *traces.Span) analyzer.Result { insecureFields := make([]analyzer.Error, 0) for _, field := range httpFields { if !strings.HasPrefix(span.Attributes.Get(field), "https") { diff --git a/server/linter/rules/ensure_attribute_naming_rule.go b/server/linter/rules/ensure_attribute_naming_rule.go index ff6d24dc1c..930ee3ff04 100644 --- a/server/linter/rules/ensure_attribute_naming_rule.go +++ b/server/linter/rules/ensure_attribute_naming_rule.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type ensureAttributeNamingRule struct{} @@ -20,7 +20,7 @@ func (r ensureAttributeNamingRule) ID() string { return analyzer.EnsureAttributeNamingRuleID } -func (r ensureAttributeNamingRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r ensureAttributeNamingRule) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { regex := regexp.MustCompile(`^([a-z0-9_]+\.)+[a-z0-9_]+$`) res := make([]analyzer.Result, 0) passed := true diff --git a/server/linter/rules/ensure_span_naming_rule.go b/server/linter/rules/ensure_span_naming_rule.go index 41f45ab663..f964746d5e 100644 --- a/server/linter/rules/ensure_span_naming_rule.go +++ b/server/linter/rules/ensure_span_naming_rule.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type ensureSpanNamingRule struct{} @@ -19,7 +19,7 @@ func (r ensureSpanNamingRule) ID() string { return analyzer.EnsureSpanNamingRuleID } -func (r ensureSpanNamingRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r ensureSpanNamingRule) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { res := make([]analyzer.Result, 0) hasErrors := false @@ -35,7 +35,7 @@ func (r ensureSpanNamingRule) Evaluate(ctx context.Context, trace model.Trace, c return analyzer.NewRuleResult(config, analyzer.EvalRuleResult{Passed: !hasErrors, Results: res}), nil } -func (r ensureSpanNamingRule) validateSpanName(ctx context.Context, span *model.Span) analyzer.Result { +func (r ensureSpanNamingRule) validateSpanName(ctx context.Context, span *traces.Span) analyzer.Result { switch span.Attributes.Get("tracetest.span.type") { case "http": return r.validateHTTPSpanName(ctx, span) @@ -53,13 +53,13 @@ func (r ensureSpanNamingRule) validateSpanName(ctx context.Context, span *model. } } -func (r ensureSpanNamingRule) validateHTTPSpanName(ctx context.Context, span *model.Span) analyzer.Result { +func (r ensureSpanNamingRule) validateHTTPSpanName(ctx context.Context, span *traces.Span) analyzer.Result { expectedName := "" - if span.Kind == model.SpanKindServer { + if span.Kind == traces.SpanKindServer { expectedName = fmt.Sprintf("%s %s", span.Attributes.Get("http.method"), span.Attributes.Get("http.route")) } - if span.Kind == model.SpanKindClient { + if span.Kind == traces.SpanKindClient { expectedName = span.Attributes.Get("http.method") } @@ -83,7 +83,7 @@ func (r ensureSpanNamingRule) validateHTTPSpanName(ctx context.Context, span *mo } } -func (r ensureSpanNamingRule) validateDatabaseSpanName(ctx context.Context, span *model.Span) analyzer.Result { +func (r ensureSpanNamingRule) validateDatabaseSpanName(ctx context.Context, span *traces.Span) analyzer.Result { dbOperation := span.Attributes.Get("db.operation") dbName := span.Attributes.Get("db.name") tableName := span.Attributes.Get("db.sql.table") @@ -119,7 +119,7 @@ func (r ensureSpanNamingRule) validateDatabaseSpanName(ctx context.Context, span } } -func (r ensureSpanNamingRule) validateRPCSpanName(ctx context.Context, span *model.Span) analyzer.Result { +func (r ensureSpanNamingRule) validateRPCSpanName(ctx context.Context, span *traces.Span) analyzer.Result { rpcService := span.Attributes.Get("rpc.service") rpcMethod := span.Attributes.Get("rpc.method") @@ -145,7 +145,7 @@ func (r ensureSpanNamingRule) validateRPCSpanName(ctx context.Context, span *mod } } -func (r ensureSpanNamingRule) validateMessagingSpanName(ctx context.Context, span *model.Span) analyzer.Result { +func (r ensureSpanNamingRule) validateMessagingSpanName(ctx context.Context, span *traces.Span) analyzer.Result { destination := span.Attributes.Get("messaging.destination") operation := span.Attributes.Get("messaging.operation") diff --git a/server/linter/rules/ensures_no_api_key_leak.go b/server/linter/rules/ensures_no_api_key_leak.go index 998c61aa50..a76cddbaf9 100644 --- a/server/linter/rules/ensures_no_api_key_leak.go +++ b/server/linter/rules/ensures_no_api_key_leak.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type ensuresNoApiKeyLeakRule struct{} @@ -24,7 +24,7 @@ func (r ensuresNoApiKeyLeakRule) ID() string { return analyzer.EnsuresNoApiKeyLeakRuleID } -func (r ensuresNoApiKeyLeakRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r ensuresNoApiKeyLeakRule) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { passed := true res := make([]analyzer.Result, 0) @@ -43,7 +43,7 @@ func (r ensuresNoApiKeyLeakRule) Evaluate(ctx context.Context, trace model.Trace return analyzer.NewRuleResult(config, analyzer.EvalRuleResult{Passed: passed, Results: res}), nil } -func (r ensuresNoApiKeyLeakRule) validate(span *model.Span) analyzer.Result { +func (r ensuresNoApiKeyLeakRule) validate(span *traces.Span) analyzer.Result { leakedFields := make([]analyzer.Error, 0) for _, field := range httpHeadersFields { requestHeader := fmt.Sprintf("%s%s", httpRequestHeader, field) diff --git a/server/linter/rules/not_empty_attribute_rule.go b/server/linter/rules/not_empty_attribute_rule.go index 89ea2c35ab..d7fec1e835 100644 --- a/server/linter/rules/not_empty_attribute_rule.go +++ b/server/linter/rules/not_empty_attribute_rule.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type notEmptyRuleAttributesRule struct{} @@ -18,7 +18,7 @@ func (r notEmptyRuleAttributesRule) ID() string { return analyzer.NotEmptyAttributesRuleID } -func (r notEmptyRuleAttributesRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r notEmptyRuleAttributesRule) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { res := make([]analyzer.Result, 0, len(trace.Flat)) passed := true diff --git a/server/linter/rules/required_attribute_rule.go b/server/linter/rules/required_attribute_rule.go index 39d95657e4..a5f3882b90 100644 --- a/server/linter/rules/required_attribute_rule.go +++ b/server/linter/rules/required_attribute_rule.go @@ -4,7 +4,7 @@ import ( "context" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type requiredAttributesRule struct{} @@ -17,7 +17,7 @@ func (r requiredAttributesRule) ID() string { return analyzer.RequiredAttributesRuleID } -func (r requiredAttributesRule) Evaluate(ctx context.Context, trace model.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { +func (r requiredAttributesRule) Evaluate(ctx context.Context, trace traces.Trace, config analyzer.LinterRule) (analyzer.RuleResult, error) { res := make([]analyzer.Result, 0) var allPassed bool = true diff --git a/server/linter/rules/required_attribute_rule_test.go b/server/linter/rules/required_attribute_rule_test.go index 54d19af302..2b2e6f574b 100644 --- a/server/linter/rules/required_attribute_rule_test.go +++ b/server/linter/rules/required_attribute_rule_test.go @@ -6,8 +6,8 @@ import ( "github.com/kubeshop/tracetest/server/linter/analyzer" "github.com/kubeshop/tracetest/server/linter/rules" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/trace" ) @@ -53,9 +53,9 @@ func TestRequiredAttributesRule(t *testing.T) { }) } -func traceWithSpans(spans ...model.Span) model.Trace { - trace := model.Trace{ - Flat: make(map[trace.SpanID]*model.Span, 0), +func traceWithSpans(spans ...traces.Span) traces.Trace { + trace := traces.Trace{ + Flat: make(map[trace.SpanID]*traces.Span, 0), } for _, span := range spans { @@ -67,9 +67,9 @@ func traceWithSpans(spans ...model.Span) model.Trace { return trace } -func spanWithAttributes(spanType string, attributes map[string]string) model.Span { - span := model.Span{ - Attributes: make(model.Attributes, 0), +func spanWithAttributes(spanType string, attributes map[string]string) traces.Span { + span := traces.Span{ + Attributes: make(traces.Attributes, 0), } for name, value := range attributes { diff --git a/server/linter/rules/required_attributes.go b/server/linter/rules/required_attributes.go index a5e8374d5f..c460ca6482 100644 --- a/server/linter/rules/required_attributes.go +++ b/server/linter/rules/required_attributes.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) var ( @@ -19,7 +19,7 @@ var ( faasAttrClient = []string{"faas.invoked_name", "faas.invoked_provider"} ) -func (r requiredAttributesRule) validateSpan(span *model.Span) analyzer.Result { +func (r requiredAttributesRule) validateSpan(span *traces.Span) analyzer.Result { switch span.Attributes.Get("tracetest.span.type") { case "http": return r.validateHttpSpan(span) @@ -39,16 +39,16 @@ func (r requiredAttributesRule) validateSpan(span *model.Span) analyzer.Result { } } -func (r requiredAttributesRule) validateHttpSpan(span *model.Span) analyzer.Result { +func (r requiredAttributesRule) validateHttpSpan(span *traces.Span) analyzer.Result { missingAttrs := r.getMissingAttrs(span, httpAttr, "http") result := analyzer.Result{ Passed: true, SpanID: span.ID.String(), } - if span.Kind == model.SpanKindClient { + if span.Kind == traces.SpanKindClient { missingAttrs = append(missingAttrs, r.getMissingAttrs(span, httpAttrClient, "http")...) - } else if span.Kind == model.SpanKindServer { + } else if span.Kind == traces.SpanKindServer { missingAttrs = append(missingAttrs, r.getMissingAttrs(span, httpAttrServer, "http")...) } @@ -60,7 +60,7 @@ func (r requiredAttributesRule) validateHttpSpan(span *model.Span) analyzer.Resu return result } -func (r requiredAttributesRule) getMissingAttrs(span *model.Span, matchingAttrList []string, spanType string) []analyzer.Error { +func (r requiredAttributesRule) getMissingAttrs(span *traces.Span, matchingAttrList []string, spanType string) []analyzer.Error { missingAttributes := make([]analyzer.Error, 0) for _, requiredAttribute := range matchingAttrList { if _, attributeExists := span.Attributes[requiredAttribute]; !attributeExists { @@ -74,7 +74,7 @@ func (r requiredAttributesRule) getMissingAttrs(span *model.Span, matchingAttrLi return missingAttributes } -func (r requiredAttributesRule) validateDatabaseSpan(span *model.Span) analyzer.Result { +func (r requiredAttributesRule) validateDatabaseSpan(span *traces.Span) analyzer.Result { missingAttrs := r.getMissingAttrs(span, databaseAttr, "database") result := analyzer.Result{ Passed: true, @@ -89,7 +89,7 @@ func (r requiredAttributesRule) validateDatabaseSpan(span *model.Span) analyzer. return result } -func (r requiredAttributesRule) validateRPCSpan(span *model.Span) analyzer.Result { +func (r requiredAttributesRule) validateRPCSpan(span *traces.Span) analyzer.Result { missingAttrs := r.getMissingAttrs(span, rpcAttr, "rpc") result := analyzer.Result{ Passed: true, @@ -104,7 +104,7 @@ func (r requiredAttributesRule) validateRPCSpan(span *model.Span) analyzer.Resul return result } -func (r requiredAttributesRule) validateMessagingSpan(span *model.Span) analyzer.Result { +func (r requiredAttributesRule) validateMessagingSpan(span *traces.Span) analyzer.Result { missingAttrs := r.getMissingAttrs(span, messagingAttr, "messaging") result := analyzer.Result{ Passed: true, @@ -119,16 +119,16 @@ func (r requiredAttributesRule) validateMessagingSpan(span *model.Span) analyzer return result } -func (r requiredAttributesRule) validateFaasSpan(span *model.Span) analyzer.Result { +func (r requiredAttributesRule) validateFaasSpan(span *traces.Span) analyzer.Result { missingAttrs := make([]analyzer.Error, 0) result := analyzer.Result{ Passed: true, SpanID: span.ID.String(), } - if span.Kind == model.SpanKindClient { + if span.Kind == traces.SpanKindClient { missingAttrs = r.getMissingAttrs(span, faasAttrClient, "faas") - } else if span.Kind == model.SpanKindServer { + } else if span.Kind == traces.SpanKindServer { missingAttrs = r.getMissingAttrs(span, faasAttrServer, "faas") } diff --git a/server/linter/rules/rules_registry.go b/server/linter/rules/rules_registry.go index 3421c9023f..13e38bfd3f 100644 --- a/server/linter/rules/rules_registry.go +++ b/server/linter/rules/rules_registry.go @@ -5,12 +5,12 @@ import ( "fmt" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" ) type Rule interface { ID() string - Evaluate(context.Context, model.Trace, analyzer.LinterRule) (analyzer.RuleResult, error) + Evaluate(context.Context, traces.Trace, analyzer.LinterRule) (analyzer.RuleResult, error) } type RuleRegistry struct { diff --git a/server/model/traceid.go b/server/model/traceid.go deleted file mode 100644 index a1920e90b7..0000000000 --- a/server/model/traceid.go +++ /dev/null @@ -1,11 +0,0 @@ -package model - -const TriggerTypeTRACEID TriggerType = "traceid" - -type TRACEIDRequest struct { - ID string `expr_enabled:"true"` -} - -type TRACEIDResponse struct { - ID string -} diff --git a/server/model/traces_test.go b/server/model/traces_test.go deleted file mode 100644 index 40bdb05e72..0000000000 --- a/server/model/traces_test.go +++ /dev/null @@ -1,253 +0,0 @@ -package model_test - -import ( - "encoding/json" - "testing" - "time" - - "github.com/kubeshop/tracetest/server/model" - "github.com/kubeshop/tracetest/server/pkg/id" - "github.com/kubeshop/tracetest/server/traces" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - v1 "go.opentelemetry.io/proto/otlp/trace/v1" -) - -func TestTraces(t *testing.T) { - rootSpan := newSpan("Root") - childSpan1 := newSpan("child 1", withParent(&rootSpan)) - childSpan2 := newSpan("child 2", withParent(&rootSpan)) - grandchildSpan := newSpan("grandchild", withParent(&childSpan2)) - - spans := []model.Span{rootSpan, childSpan1, childSpan2, grandchildSpan} - trace := model.NewTrace("trace", spans) - - assert.Len(t, trace.Flat, 4) - assert.Equal(t, "Root", trace.RootSpan.Name) - assert.Equal(t, "child 1", trace.RootSpan.Children[0].Name) - assert.Equal(t, "child 2", trace.RootSpan.Children[1].Name) - assert.Equal(t, "grandchild", trace.RootSpan.Children[1].Children[0].Name) -} - -func TestTraceWithMultipleRoots(t *testing.T) { - root1 := newSpan("Root 1") - root1Child := newSpan("Child from root 1", withParent(&root1)) - root2 := newSpan("Root 2") - root2Child := newSpan("Child from root 2", withParent(&root2)) - root3 := newSpan("Root 3") - root3Child := newSpan("Child from root 3", withParent(&root3)) - - spans := []model.Span{root1, root1Child, root2, root2Child, root3, root3Child} - trace := model.NewTrace("trace", spans) - - // agreggate root + 3 roots + 3 child - assert.Len(t, trace.Flat, 7) - assert.Equal(t, model.TemporaryRootSpanName, trace.RootSpan.Name) - assert.Equal(t, "Root 1", trace.RootSpan.Children[0].Name) - assert.Equal(t, "Root 2", trace.RootSpan.Children[1].Name) - assert.Equal(t, "Root 3", trace.RootSpan.Children[2].Name) - assert.Equal(t, "Child from root 1", trace.RootSpan.Children[0].Children[0].Name) - assert.Equal(t, "Child from root 2", trace.RootSpan.Children[1].Children[0].Name) - assert.Equal(t, "Child from root 3", trace.RootSpan.Children[2].Children[0].Name) -} - -func TestTraceWithMultipleRootsFromOtel(t *testing.T) { - root1 := newOtelSpan("Root 1", nil) - root1Child := newOtelSpan("Child from root 1", root1) - root2 := newOtelSpan("Root 2", nil) - root2Child := newOtelSpan("Child from root 2", root2) - root3 := newOtelSpan("Root 3", nil) - root3Child := newOtelSpan("Child from root 3", root3) - - tracesData := &v1.TracesData{ - ResourceSpans: []*v1.ResourceSpans{ - { - ScopeSpans: []*v1.ScopeSpans{ - { - Spans: []*v1.Span{root1, root1Child, root2, root2Child, root3, root3Child}, - }, - }, - }, - }, - } - - trace := traces.FromOtel(tracesData) - - // agreggate root + 3 roots + 3 child - assert.Len(t, trace.Flat, 7) - assert.Equal(t, model.TemporaryRootSpanName, trace.RootSpan.Name) - assert.Equal(t, "Root 1", trace.RootSpan.Children[0].Name) - assert.Equal(t, "Root 2", trace.RootSpan.Children[1].Name) - assert.Equal(t, "Root 3", trace.RootSpan.Children[2].Name) - assert.Equal(t, "Child from root 1", trace.RootSpan.Children[0].Children[0].Name) - assert.Equal(t, "Child from root 2", trace.RootSpan.Children[1].Children[0].Name) - assert.Equal(t, "Child from root 3", trace.RootSpan.Children[2].Children[0].Name) -} - -func TestInjectingNewRootWhenSingleRoot(t *testing.T) { - rootSpan := newSpan("Root") - childSpan1 := newSpan("child 1", withParent(&rootSpan)) - childSpan2 := newSpan("child 2", withParent(&rootSpan)) - grandchildSpan := newSpan("grandchild", withParent(&childSpan2)) - - spans := []model.Span{rootSpan, childSpan1, childSpan2, grandchildSpan} - trace := model.NewTrace("trace", spans) - - newRoot := newSpan("new Root") - newTrace := trace.InsertRootSpan(newRoot) - - assert.Len(t, newTrace.Flat, 5) - assert.Equal(t, "new Root", newTrace.RootSpan.Name) - assert.Len(t, newTrace.RootSpan.Children, 1) - assert.Equal(t, "Root", newTrace.RootSpan.Children[0].Name) -} - -func TestInjectingNewRootWhenMultipleRoots(t *testing.T) { - root1 := newSpan("Root 1") - root1Child := newSpan("Child from root 1", withParent(&root1)) - root2 := newSpan("Root 2") - root2Child := newSpan("Child from root 2", withParent(&root2)) - root3 := newSpan("Root 3") - root3Child := newSpan("Child from root 3", withParent(&root3)) - - spans := []model.Span{root1, root1Child, root2, root2Child, root3, root3Child} - trace := model.NewTrace("trace", spans) - - for _, oldRoot := range trace.RootSpan.Children { - require.NotNil(t, oldRoot.Parent) - } - - newRoot := newSpan("new Root") - newTrace := trace.InsertRootSpan(newRoot) - - assert.Len(t, newTrace.Flat, 7) - assert.Equal(t, "new Root", newTrace.RootSpan.Name) - assert.Len(t, newTrace.RootSpan.Children, 3) - assert.Equal(t, "Root 1", newTrace.RootSpan.Children[0].Name) - assert.Equal(t, "Root 2", newTrace.RootSpan.Children[1].Name) - assert.Equal(t, "Root 3", newTrace.RootSpan.Children[2].Name) - - for _, oldRoot := range trace.RootSpan.Children { - require.NotNil(t, oldRoot.Parent) - assert.Equal(t, newRoot.ID.String(), oldRoot.Parent.ID.String()) - } -} - -func TestNoTemporaryRootIfTracetestRootExists(t *testing.T) { - root1 := newSpan("Root 1") - root1Child := newSpan("Child from root 1", withParent(&root1)) - root2 := newSpan(model.TriggerSpanName) - root2Child := newSpan("Child from root 2", withParent(&root2)) - root3 := newSpan("Root 3") - root3Child := newSpan("Child from root 3", withParent(&root3)) - - spans := []model.Span{root1, root1Child, root2, root2Child, root3, root3Child} - trace := model.NewTrace("trace", spans) - - assert.Equal(t, root2.ID, trace.RootSpan.ID) - assert.Equal(t, root2.Name, trace.RootSpan.Name) -} - -func TestNoTemporaryRootIfATemporaryRootExists(t *testing.T) { - root1 := newSpan("Root 1") - root1Child := newSpan("Child from root 1", withParent(&root1)) - root2 := newSpan(model.TemporaryRootSpanName) - root2Child := newSpan("Child from root 2", withParent(&root2)) - root3 := newSpan("Root 3") - root3Child := newSpan("Child from root 3", withParent(&root3)) - - spans := []model.Span{root1, root1Child, root2, root2Child, root3, root3Child} - trace := model.NewTrace("trace", spans) - - assert.Equal(t, root2.ID, trace.RootSpan.ID) - assert.Equal(t, root2.Name, trace.RootSpan.Name) -} - -func TestTriggerSpanShouldBeRootWhenTemporaryRootExistsToo(t *testing.T) { - root1 := newSpan(model.TriggerSpanName) - root1Child := newSpan("Child from root 1", withParent(&root1)) - root2 := newSpan(model.TemporaryRootSpanName) - root2Child := newSpan("Child from root 2", withParent(&root2)) - root3 := newSpan("Root 3") - root3Child := newSpan("Child from root 3", withParent(&root3)) - - spans := []model.Span{root1, root1Child, root2, root2Child, root3, root3Child} - trace := model.NewTrace("trace", spans) - - assert.Equal(t, root1.ID, trace.RootSpan.ID) - assert.Equal(t, root1.Name, trace.RootSpan.Name) -} - -func TestEventsAreInjectedIntoAttributes(t *testing.T) { - rootSpan := newSpan("Root", withEvents([]model.SpanEvent{ - {Name: "event 1", Attributes: model.Attributes{"attribute1": "value"}}, - {Name: "event 2", Attributes: model.Attributes{"attribute2": "value"}}, - })) - childSpan1 := newSpan("child 1", withParent(&rootSpan)) - childSpan2 := newSpan("child 2", withParent(&rootSpan)) - grandchildSpan := newSpan("grandchild", withParent(&childSpan2)) - - spans := []model.Span{rootSpan, childSpan1, childSpan2, grandchildSpan} - trace := model.NewTrace("trace", spans) - - require.NotEmpty(t, trace.RootSpan.Attributes["span.events"]) - - events := []model.SpanEvent{} - err := json.Unmarshal([]byte(trace.RootSpan.Attributes["span.events"]), &events) - require.NoError(t, err) - - assert.Equal(t, "event 1", events[0].Name) - assert.Equal(t, "value", events[0].Attributes["attribute1"]) - assert.Equal(t, "event 2", events[1].Name) - assert.Equal(t, "value", events[1].Attributes["attribute2"]) -} - -type option func(*model.Span) - -func withParent(parent *model.Span) option { - return func(s *model.Span) { - s.Parent = parent - } -} - -func withEvents(events []model.SpanEvent) option { - return func(s *model.Span) { - s.Events = events - } -} - -func newSpan(name string, options ...option) model.Span { - span := model.Span{ - ID: id.NewRandGenerator().SpanID(), - Name: name, - Attributes: make(model.Attributes), - StartTime: time.Now(), - EndTime: time.Now().Add(1 * time.Second), - } - - for _, option := range options { - option(&span) - } - - if span.Parent != nil { - span.Attributes[model.TracetestMetadataFieldParentID] = span.Parent.ID.String() - } - - return span -} - -func newOtelSpan(name string, parent *v1.Span) *v1.Span { - id := id.NewRandGenerator().SpanID() - var parentId []byte = nil - if parent != nil { - parentId = parent.SpanId - } - - return &v1.Span{ - SpanId: id[:], - Name: name, - ParentSpanId: parentId, - StartTimeUnixNano: uint64(time.Now().UnixNano()), - EndTimeUnixNano: uint64(time.Now().Add(1 * time.Second).UnixNano()), - } -} diff --git a/server/otlp/ingester.go b/server/otlp/ingester.go index 89a3af02e4..2fcf8ea048 100644 --- a/server/otlp/ingester.go +++ b/server/otlp/ingester.go @@ -8,7 +8,6 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/executor" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/model/events" "github.com/kubeshop/tracetest/server/test" "github.com/kubeshop/tracetest/server/traces" @@ -21,7 +20,7 @@ type runGetter interface { } type tracePersister interface { - SaveTrace(context.Context, model.Trace) error + SaveTrace(context.Context, traces.Trace) error } type ingester struct { @@ -66,7 +65,7 @@ func (i ingester) Ingest(ctx context.Context, request *pb.ExportTraceServiceRequ return &pb.ExportTraceServiceResponse{}, nil } -func (i ingester) notify(ctx context.Context, trace model.Trace, requestType string) error { +func (i ingester) notify(ctx context.Context, trace traces.Trace, requestType string) error { run, err := i.runGetter.GetRunByTraceID(ctx, trace.ID) if errors.Is(err, sql.ErrNoRows) { // trace is not part of any known test run, so no need to notify diff --git a/server/test/run.go b/server/test/run.go index b9317c98bd..2b0d4f0181 100644 --- a/server/test/run.go +++ b/server/test/run.go @@ -7,10 +7,10 @@ import ( "github.com/kubeshop/tracetest/server/executor/testrunner" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" "github.com/kubeshop/tracetest/server/pkg/maps" "github.com/kubeshop/tracetest/server/test/trigger" + "github.com/kubeshop/tracetest/server/traces" "github.com/kubeshop/tracetest/server/variableset" ) @@ -99,7 +99,7 @@ func (r Run) SuccessfullyTriggered() Run { return r } -func (r Run) SuccessfullyPolledTraces(t *model.Trace) Run { +func (r Run) SuccessfullyPolledTraces(t *traces.Trace) Run { r.State = RunStateAnalyzingTrace r.Trace = t r.ObtainedTraceAt = time.Now() @@ -203,13 +203,13 @@ func (r Run) GenerateRequiredGateResult(gates []testrunner.RequiredGate) testrun return requiredGatesResult } -func NewTracetestRootSpan(run Run) model.Span { - return model.AugmentRootSpan(model.Span{ +func NewTracetestRootSpan(run Run) traces.Span { + return traces.AugmentRootSpan(traces.Span{ ID: id.NewRandGenerator().SpanID(), - Name: model.TriggerSpanName, + Name: traces.TriggerSpanName, StartTime: run.ServiceTriggeredAt, EndTime: run.ServiceTriggerCompletedAt, - Attributes: model.Attributes{}, - Children: []*model.Span{}, + Attributes: traces.Attributes{}, + Children: []*traces.Span{}, }, run.TriggerResult) } diff --git a/server/test/test_entities.go b/server/test/test_entities.go index 48d8f50351..c998054bee 100644 --- a/server/test/test_entities.go +++ b/server/test/test_entities.go @@ -6,10 +6,10 @@ import ( "github.com/kubeshop/tracetest/server/executor/testrunner" "github.com/kubeshop/tracetest/server/linter/analyzer" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" "github.com/kubeshop/tracetest/server/pkg/maps" "github.com/kubeshop/tracetest/server/test/trigger" + "github.com/kubeshop/tracetest/server/traces" "github.com/kubeshop/tracetest/server/variableset" "go.opentelemetry.io/otel/trace" ) @@ -115,7 +115,7 @@ type ( // result info TriggerResult trigger.TriggerResult Results *RunResults - Trace *model.Trace + Trace *traces.Trace Outputs maps.Ordered[string, RunOutput] LastError error Pass int diff --git a/server/tracedb/awsxray.go b/server/tracedb/awsxray.go index 5e1c3c34a7..ce38d45d80 100644 --- a/server/tracedb/awsxray.go +++ b/server/tracedb/awsxray.go @@ -20,6 +20,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" conventions "go.opentelemetry.io/collector/semconv/v1.6.1" "go.opentelemetry.io/otel/trace" ) @@ -110,15 +111,15 @@ func (db *awsxrayDB) TestConnection(ctx context.Context) model.ConnectionResult return tester.TestConnection(ctx) } -func (db *awsxrayDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (db *awsxrayDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { hexTraceID, err := trace.TraceIDFromHex(traceID) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } parsedTraceID, err := convertToAmazonTraceID(hexTraceID) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } res, err := db.service.BatchGetTraces(&xray.BatchGetTracesInput{ @@ -126,58 +127,58 @@ func (db *awsxrayDB) GetTraceByID(ctx context.Context, traceID string) (model.Tr }) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } if len(res.Traces) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } return parseXRayTrace(traceID, res.Traces[0]) } -func parseXRayTrace(traceID string, rawTrace *xray.Trace) (model.Trace, error) { +func parseXRayTrace(traceID string, rawTrace *xray.Trace) (traces.Trace, error) { if len(rawTrace.Segments) == 0 { - return model.Trace{}, nil + return traces.Trace{}, nil } - spans := []model.Span{} + spans := []traces.Span{} for _, segment := range rawTrace.Segments { newSpans, err := parseSegmentToSpans([]byte(*segment.Document), traceID) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } spans = append(spans, newSpans...) } - return model.NewTrace(traceID, spans), nil + return traces.NewTrace(traceID, spans), nil } -func parseSegmentToSpans(rawSeg []byte, traceID string) ([]model.Span, error) { +func parseSegmentToSpans(rawSeg []byte, traceID string) ([]traces.Span, error) { var seg segment err := json.Unmarshal(rawSeg, &seg) if err != nil { - return []model.Span{}, err + return []traces.Span{}, err } err = seg.Validate() if err != nil { - return []model.Span{}, err + return []traces.Span{}, err } return segToSpans(seg, traceID, nil) } -func segToSpans(seg segment, traceID string, parent *model.Span) ([]model.Span, error) { +func segToSpans(seg segment, traceID string, parent *traces.Span) ([]traces.Span, error) { span, err := generateSpan(&seg, parent) if err != nil { - return []model.Span{}, err + return []traces.Span{}, err } - spans := []model.Span{span} + spans := []traces.Span{span} for _, s := range seg.Subsegments { nestedSpans, err := segToSpans(s, traceID, &span) @@ -192,9 +193,9 @@ func segToSpans(seg segment, traceID string, parent *model.Span) ([]model.Span, return spans, nil } -func generateSpan(seg *segment, parent *model.Span) (model.Span, error) { - attributes := make(model.Attributes, 0) - span := model.Span{ +func generateSpan(seg *segment, parent *traces.Span) (traces.Span, error) { + attributes := make(traces.Attributes, 0) + span := traces.Span{ Parent: parent, Name: *seg.Name, } @@ -205,9 +206,9 @@ func generateSpan(seg *segment, parent *model.Span) (model.Span, error) { return span, err } - attributes[model.TracetestMetadataFieldParentID] = parentID.String() + attributes[traces.TracetestMetadataFieldParentID] = parentID.String() } else if parent != nil { - attributes[model.TracetestMetadataFieldParentID] = parent.ID.String() + attributes[traces.TracetestMetadataFieldParentID] = parent.ID.String() } // decode span id @@ -219,7 +220,7 @@ func generateSpan(seg *segment, parent *model.Span) (model.Span, error) { err = addNamespace(seg, attributes) if err != nil { - return model.Span{}, err + return traces.Span{}, err } span.StartTime = floatSecToTime(seg.StartTime) @@ -236,7 +237,7 @@ func generateSpan(seg *segment, parent *model.Span) (model.Span, error) { addAWSToSpan(seg.AWS, attributes) err = addSQLToSpan(seg.SQL, attributes) if err != nil { - return model.Span{}, err + return traces.Span{}, err } if seg.Traced != nil { @@ -258,7 +259,7 @@ const ( validRemoteNamespace = "remote" ) -func addNamespace(seg *segment, attributes model.Attributes) error { +func addNamespace(seg *segment, attributes traces.Attributes) error { if seg.Namespace != nil { switch *seg.Namespace { case validAWSNamespace: @@ -275,7 +276,7 @@ func addNamespace(seg *segment, attributes model.Attributes) error { return nil } -func addHTTP(seg *segment, attributes model.Attributes) { +func addHTTP(seg *segment, attributes traces.Attributes) { if seg.HTTP == nil { return } @@ -306,7 +307,7 @@ func addHTTP(seg *segment, attributes model.Attributes) { } } -func addAWSToSpan(aws *aWSData, attrs model.Attributes) { +func addAWSToSpan(aws *aWSData, attrs traces.Attributes) { if aws != nil { attrs.SetPointerValue(AWSAccountAttribute, aws.AccountID) attrs.SetPointerValue(AWSOperationAttribute, aws.Operation) @@ -321,7 +322,7 @@ func addAWSToSpan(aws *aWSData, attrs model.Attributes) { } } -func addSQLToSpan(sql *sQLData, attrs model.Attributes) error { +func addSQLToSpan(sql *sQLData, attrs traces.Attributes) error { if sql == nil { return nil } @@ -343,7 +344,7 @@ func addSQLToSpan(sql *sQLData, attrs model.Attributes) error { return nil } -func addAnnotations(annos map[string]interface{}, attrs model.Attributes) { +func addAnnotations(annos map[string]interface{}, attrs traces.Attributes) { if len(annos) > 0 { for k, v := range annos { switch t := v.(type) { @@ -367,7 +368,7 @@ func addAnnotations(annos map[string]interface{}, attrs model.Attributes) { } } -func addMetadata(meta map[string]map[string]interface{}, attrs model.Attributes) error { +func addMetadata(meta map[string]map[string]interface{}, attrs traces.Attributes) error { for k, v := range meta { val, err := json.Marshal(v) if err != nil { diff --git a/server/tracedb/azureappinsights.go b/server/tracedb/azureappinsights.go index 7144075e63..f66d532b8c 100644 --- a/server/tracedb/azureappinsights.go +++ b/server/tracedb/azureappinsights.go @@ -14,6 +14,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) @@ -96,7 +97,7 @@ func (db *azureAppInsightsDB) TestConnection(ctx context.Context) model.Connecti return tester.TestConnection(ctx) } -func (db *azureAppInsightsDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (db *azureAppInsightsDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { query := fmt.Sprintf("union * | where operation_Id == '%s'", traceID) body := azquery.Body{ Query: &query, @@ -104,12 +105,12 @@ func (db *azureAppInsightsDB) GetTraceByID(ctx context.Context, traceID string) res, err := db.client.QueryResource(ctx, db.resourceArmId, body, nil) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } table := res.Tables[0] if len(table.Rows) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } return parseAzureAppInsightsTrace(traceID, table) @@ -182,16 +183,16 @@ func newSpanRow(row azquery.Row, columns []*azquery.Column) spanRow { return spanRow{values} } -func parseAzureAppInsightsTrace(traceID string, table *azquery.Table) (model.Trace, error) { +func parseAzureAppInsightsTrace(traceID string, table *azquery.Table) (traces.Trace, error) { spans, err := parseSpans(table) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } - return model.NewTrace(traceID, spans), nil + return traces.NewTrace(traceID, spans), nil } -func parseSpans(table *azquery.Table) ([]model.Span, error) { +func parseSpans(table *azquery.Table) ([]traces.Span, error) { spanTable := newSpanTable(table) spanRows := spanTable.Spans() eventRows := spanTable.Events() @@ -201,11 +202,11 @@ func parseSpans(table *azquery.Table) ([]model.Span, error) { spanEventsMap[eventRow.ParentID()] = append(spanEventsMap[eventRow.ParentID()], eventRow) } - spanMap := make(map[string]*model.Span) + spanMap := make(map[string]*traces.Span) for _, spanRow := range spanRows { span, err := parseRowToSpan(spanRow) if err != nil { - return []model.Span{}, err + return []traces.Span{}, err } spanMap[span.ID.String()] = &span @@ -215,13 +216,13 @@ func parseSpans(table *azquery.Table) ([]model.Span, error) { parentSpan := spanMap[eventRow.ParentID()] event, err := parseEvent(eventRow) if err != nil { - return []model.Span{}, err + return []traces.Span{}, err } parentSpan.Events = append(parentSpan.Events, event) } - spans := make([]model.Span, 0, len(spanMap)) + spans := make([]traces.Span, 0, len(spanMap)) for _, span := range spanMap { spans = append(spans, *span) } @@ -229,8 +230,8 @@ func parseSpans(table *azquery.Table) ([]model.Span, error) { return spans, nil } -func parseEvent(row spanRow) (model.SpanEvent, error) { - event := model.SpanEvent{ +func parseEvent(row spanRow) (traces.SpanEvent, error) { + event := traces.SpanEvent{ Name: row.Get("message").(string), } @@ -241,7 +242,7 @@ func parseEvent(row spanRow) (model.SpanEvent, error) { event.Timestamp = timestamp - attributes := make(model.Attributes, 0) + attributes := make(traces.Attributes, 0) rawAttributes := row.Get("customDimensions").(string) err = json.Unmarshal([]byte(rawAttributes), &attributes) if err != nil { @@ -253,9 +254,9 @@ func parseEvent(row spanRow) (model.SpanEvent, error) { return event, nil } -func parseRowToSpan(row spanRow) (model.Span, error) { - attributes := make(model.Attributes, 0) - span := model.Span{ +func parseRowToSpan(row spanRow) (traces.Span, error) { + attributes := make(traces.Attributes, 0) + span := traces.Span{ Attributes: attributes, } var duration time.Duration @@ -301,7 +302,7 @@ func parseRowToSpan(row spanRow) (model.Span, error) { return span, nil } -func parseSpanID(span *model.Span, value any) error { +func parseSpanID(span *traces.Span, value any) error { spanID, err := trace.SpanIDFromHex(value.(string)) if err != nil { return fmt.Errorf("failed to parse spanId: %w", err) @@ -311,8 +312,8 @@ func parseSpanID(span *model.Span, value any) error { return nil } -func parseAttributes(span *model.Span, value any) error { - attributes := make(model.Attributes, 0) +func parseAttributes(span *traces.Span, value any) error { + attributes := make(traces.Attributes, 0) rawAttributes := value.(string) err := json.Unmarshal([]byte(rawAttributes), &attributes) if err != nil { @@ -325,17 +326,17 @@ func parseAttributes(span *model.Span, value any) error { return nil } -func parseParentID(span *model.Span, value any) error { +func parseParentID(span *traces.Span, value any) error { rawParentID, ok := value.(string) if ok { - span.Attributes[model.TracetestMetadataFieldParentID] = rawParentID + span.Attributes[traces.TracetestMetadataFieldParentID] = rawParentID } else { - span.Attributes[model.TracetestMetadataFieldParentID] = "" + span.Attributes[traces.TracetestMetadataFieldParentID] = "" } return nil } -func parseName(span *model.Span, value any) error { +func parseName(span *traces.Span, value any) error { rawName, ok := value.(string) if ok { span.Name = rawName @@ -345,7 +346,7 @@ func parseName(span *model.Span, value any) error { return nil } -func parseStartTime(span *model.Span, value any) error { +func parseStartTime(span *traces.Span, value any) error { rawStartTime := value.(string) startTime, err := time.Parse(time.RFC3339Nano, rawStartTime) if err != nil { diff --git a/server/tracedb/connection/trace_polling_step.go b/server/tracedb/connection/trace_polling_step.go index 895dca6622..36751cc45f 100644 --- a/server/tracedb/connection/trace_polling_step.go +++ b/server/tracedb/connection/trace_polling_step.go @@ -5,11 +5,12 @@ import ( "errors" "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) type DataStore interface { - GetTraceByID(context.Context, string) (model.Trace, error) + GetTraceByID(context.Context, string) (traces.Trace, error) GetTraceID() trace.TraceID } diff --git a/server/tracedb/datasource/datasource.go b/server/tracedb/datasource/datasource.go index 3188225833..e6d2143017 100644 --- a/server/tracedb/datasource/datasource.go +++ b/server/tracedb/datasource/datasource.go @@ -5,6 +5,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/traces" "google.golang.org/grpc" ) @@ -15,8 +16,8 @@ var ( HTTP SupportedDataSource = "http" ) -type HttpCallback func(ctx context.Context, traceID string, client *HttpClient) (model.Trace, error) -type GrpcCallback func(ctx context.Context, traceID string, connection *grpc.ClientConn) (model.Trace, error) +type HttpCallback func(ctx context.Context, traceID string, client *HttpClient) (traces.Trace, error) +type GrpcCallback func(ctx context.Context, traceID string, connection *grpc.ClientConn) (traces.Trace, error) type Callbacks struct { GRPC GrpcCallback @@ -27,15 +28,15 @@ type DataSource interface { Endpoint() string Connect(ctx context.Context) error Ready() bool - GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) + GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) TestConnection(ctx context.Context) model.ConnectionTestStep Close() error } type noopDataSource struct{} -func (dataSource *noopDataSource) GetTraceByID(ctx context.Context, traceID string) (t model.Trace, err error) { - return model.Trace{}, nil +func (dataSource *noopDataSource) GetTraceByID(ctx context.Context, traceID string) (t traces.Trace, err error) { + return traces.Trace{}, nil } func (db *noopDataSource) Endpoint() string { return "" } func (db *noopDataSource) Connect(ctx context.Context) error { return nil } diff --git a/server/tracedb/datasource/grpc.go b/server/tracedb/datasource/grpc.go index 28c8510b03..8586b91037 100644 --- a/server/tracedb/datasource/grpc.go +++ b/server/tracedb/datasource/grpc.go @@ -7,6 +7,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" "github.com/pkg/errors" "go.opentelemetry.io/collector/component/componenttest" "go.opentelemetry.io/collector/config/configcompression" @@ -75,7 +76,7 @@ func (client *GrpcClient) Ready() bool { return client.conn != nil } -func (client *GrpcClient) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (client *GrpcClient) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { return client.callback(ctx, traceID, client.conn) } diff --git a/server/tracedb/datasource/http.go b/server/tracedb/datasource/http.go index 6053cd7bd5..0315018af4 100644 --- a/server/tracedb/datasource/http.go +++ b/server/tracedb/datasource/http.go @@ -15,6 +15,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" ) type HttpClient struct { @@ -58,7 +59,7 @@ func (client *HttpClient) Close() error { return nil } -func (client *HttpClient) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (client *HttpClient) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { return client.callback(ctx, traceID, client) } diff --git a/server/tracedb/elasticsearchdb.go b/server/tracedb/elasticsearchdb.go index 12e0188c54..c1f497a052 100644 --- a/server/tracedb/elasticsearchdb.go +++ b/server/tracedb/elasticsearchdb.go @@ -17,6 +17,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) @@ -65,9 +66,9 @@ func (db *elasticsearchDB) Ready() bool { return db.client != nil } -func (db *elasticsearchDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (db *elasticsearchDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { if !db.Ready() { - return model.Trace{}, fmt.Errorf("ElasticSearch dataStore not ready") + return traces.Trace{}, fmt.Errorf("ElasticSearch dataStore not ready") } content := strings.NewReader(fmt.Sprintf(`{ "query": { "match": { "trace.id": "%s" } } @@ -81,23 +82,23 @@ func (db *elasticsearchDB) GetTraceByID(ctx context.Context, traceID string) (mo response, err := searchRequest.Do(ctx, db.client) if err != nil { - return model.Trace{}, fmt.Errorf("could not execute search request: %w", err) + return traces.Trace{}, fmt.Errorf("could not execute search request: %w", err) } defer response.Body.Close() responseBody, err := ioutil.ReadAll(response.Body) if err != nil { - return model.Trace{}, fmt.Errorf("could not read response body") + return traces.Trace{}, fmt.Errorf("could not read response body") } var searchResponse searchResponse err = json.Unmarshal(responseBody, &searchResponse) if err != nil { - return model.Trace{}, fmt.Errorf("could not unmarshal search response into struct: %w", err) + return traces.Trace{}, fmt.Errorf("could not unmarshal search response into struct: %w", err) } if len(searchResponse.Hits.Results) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } return convertElasticSearchFormatIntoTrace(traceID, searchResponse), nil @@ -152,17 +153,17 @@ func getClusterInfo(client *elasticsearch.Client) (string, error) { return info, nil } -func convertElasticSearchFormatIntoTrace(traceID string, searchResponse searchResponse) model.Trace { - spans := make([]model.Span, 0) +func convertElasticSearchFormatIntoTrace(traceID string, searchResponse searchResponse) traces.Trace { + spans := make([]traces.Span, 0) for _, result := range searchResponse.Hits.Results { span := convertElasticSearchSpanIntoSpan(result.Source) spans = append(spans, span) } - return model.NewTrace(traceID, spans) + return traces.NewTrace(traceID, spans) } -func convertElasticSearchSpanIntoSpan(input map[string]interface{}) model.Span { +func convertElasticSearchSpanIntoSpan(input map[string]interface{}) traces.Span { opts := &FlattenOptions{Delimiter: "."} flatInput, _ := flatten(opts.Prefix, 0, input, opts) @@ -204,7 +205,7 @@ func convertElasticSearchSpanIntoSpan(input map[string]interface{}) model.Span { endTime := startTime.Add(time.Microsecond * time.Duration(duration)) // Attributes - attributes := make(model.Attributes, 0) + attributes := make(traces.Attributes, 0) for attrName, attrValue := range flatInput { name := attrName @@ -216,17 +217,17 @@ func convertElasticSearchSpanIntoSpan(input map[string]interface{}) model.Span { // ParentId parentId := flatInput["parent.id"] if parentId != nil { - attributes[model.TracetestMetadataFieldParentID] = flatInput["parent.id"].(string) + attributes[traces.TracetestMetadataFieldParentID] = flatInput["parent.id"].(string) } - return model.Span{ + return traces.Span{ ID: id, Name: name, StartTime: startTime, EndTime: endTime, Attributes: attributes, Parent: nil, - Children: []*model.Span{}, + Children: []*traces.Span{}, } } diff --git a/server/tracedb/jaegerdb.go b/server/tracedb/jaegerdb.go index aa7a984026..d6dd9db976 100644 --- a/server/tracedb/jaegerdb.go +++ b/server/tracedb/jaegerdb.go @@ -68,7 +68,7 @@ func (jtd *jaegerTraceDB) TestConnection(ctx context.Context) model.ConnectionRe return tester.TestConnection(ctx) } -func (jtd *jaegerTraceDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (jtd *jaegerTraceDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { trace, err := jtd.dataSource.GetTraceByID(ctx, traceID) return trace, err } @@ -81,14 +81,14 @@ func (jtd *jaegerTraceDB) Close() error { return jtd.dataSource.Close() } -func jaegerGrpcGetTraceByID(ctx context.Context, traceID string, conn *grpc.ClientConn) (model.Trace, error) { +func jaegerGrpcGetTraceByID(ctx context.Context, traceID string, conn *grpc.ClientConn) (traces.Trace, error) { query := pb.NewQueryServiceClient(conn) stream, err := query.GetTrace(ctx, &pb.GetTraceRequest{ TraceId: traceID, }) if err != nil { - return model.Trace{}, fmt.Errorf("jaeger get trace: %w", err) + return traces.Trace{}, fmt.Errorf("jaeger get trace: %w", err) } // jaeger-query v3 API returns otel spans @@ -102,12 +102,12 @@ func jaegerGrpcGetTraceByID(ctx context.Context, traceID string, conn *grpc.Clie if err != nil { st, ok := status.FromError(err) if !ok { - return model.Trace{}, fmt.Errorf("jaeger stream recv: %w", err) + return traces.Trace{}, fmt.Errorf("jaeger stream recv: %w", err) } if st.Message() == "trace not found" { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } - return model.Trace{}, fmt.Errorf("jaeger stream recv err: %w", err) + return traces.Trace{}, fmt.Errorf("jaeger stream recv err: %w", err) } spans = append(spans, in.ResourceSpans...) diff --git a/server/tracedb/opensearchdb.go b/server/tracedb/opensearchdb.go index d61a52ab61..0ae5906af8 100644 --- a/server/tracedb/opensearchdb.go +++ b/server/tracedb/opensearchdb.go @@ -13,6 +13,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" "github.com/opensearch-project/opensearch-go" "github.com/opensearch-project/opensearch-go/opensearchapi" "go.opentelemetry.io/otel/trace" @@ -63,9 +64,9 @@ func (db *opensearchDB) Ready() bool { return db.client != nil } -func (db *opensearchDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (db *opensearchDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { if !db.Ready() { - return model.Trace{}, fmt.Errorf("OpenSearch dataStore not ready") + return traces.Trace{}, fmt.Errorf("OpenSearch dataStore not ready") } content := strings.NewReader(fmt.Sprintf(`{ "query": { "match": { "traceId": "%s" } } @@ -78,22 +79,22 @@ func (db *opensearchDB) GetTraceByID(ctx context.Context, traceID string) (model response, err := searchRequest.Do(ctx, db.client) if err != nil { - return model.Trace{}, fmt.Errorf("could not execute search request: %w", err) + return traces.Trace{}, fmt.Errorf("could not execute search request: %w", err) } responseBody, err := ioutil.ReadAll(response.Body) if err != nil { - return model.Trace{}, fmt.Errorf("could not read response body") + return traces.Trace{}, fmt.Errorf("could not read response body") } var searchResponse searchResponse err = json.Unmarshal(responseBody, &searchResponse) if err != nil { - return model.Trace{}, fmt.Errorf("could not unmarshal search response into struct: %w", err) + return traces.Trace{}, fmt.Errorf("could not unmarshal search response into struct: %w", err) } if len(searchResponse.Hits.Results) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } return convertOpensearchFormatIntoTrace(traceID, searchResponse), nil @@ -127,23 +128,23 @@ func newOpenSearchDB(cfg *datastore.ElasticSearchConfig) (TraceDB, error) { }, nil } -func convertOpensearchFormatIntoTrace(traceID string, searchResponse searchResponse) model.Trace { - spans := make([]model.Span, 0) +func convertOpensearchFormatIntoTrace(traceID string, searchResponse searchResponse) traces.Trace { + spans := make([]traces.Span, 0) for _, result := range searchResponse.Hits.Results { span := convertOpensearchSpanIntoSpan(result.Source) spans = append(spans, span) } - return model.NewTrace(traceID, spans) + return traces.NewTrace(traceID, spans) } -func convertOpensearchSpanIntoSpan(input map[string]interface{}) model.Span { +func convertOpensearchSpanIntoSpan(input map[string]interface{}) traces.Span { spanId, _ := trace.SpanIDFromHex(input["spanId"].(string)) startTime, _ := time.Parse(time.RFC3339, input["startTime"].(string)) endTime, _ := time.Parse(time.RFC3339, input["endTime"].(string)) - attributes := make(model.Attributes, 0) + attributes := make(traces.Attributes, 0) for attrName, attrValue := range input { if !strings.HasPrefix(attrName, "span.attributes.") && !strings.HasPrefix(attrName, "resource.attributes.") { @@ -160,17 +161,17 @@ func convertOpensearchSpanIntoSpan(input map[string]interface{}) model.Span { attributes[name] = fmt.Sprintf("%v", attrValue) } - attributes[model.TracetestMetadataFieldKind] = input["kind"].(string) - attributes[model.TracetestMetadataFieldKind] = input["parentSpanId"].(string) + attributes[traces.TracetestMetadataFieldKind] = input["kind"].(string) + attributes[traces.TracetestMetadataFieldKind] = input["parentSpanId"].(string) - return model.Span{ + return traces.Span{ ID: spanId, Name: input["name"].(string), StartTime: startTime, EndTime: endTime, Attributes: attributes, Parent: nil, - Children: []*model.Span{}, + Children: []*traces.Span{}, } } diff --git a/server/tracedb/otlp.go b/server/tracedb/otlp.go index e573a814ad..d908977d3b 100644 --- a/server/tracedb/otlp.go +++ b/server/tracedb/otlp.go @@ -2,27 +2,23 @@ package tracedb import ( "context" - "strings" - "github.com/kubeshop/tracetest/server/model" - "github.com/kubeshop/tracetest/server/test" - "github.com/kubeshop/tracetest/server/tracedb/connection" "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) -type runByTraceIDGetter interface { - GetRunByTraceID(context.Context, trace.TraceID) (test.Run, error) +type traceGetter interface { + Get(context.Context, trace.TraceID) (traces.Trace, error) } type OTLPTraceDB struct { realTraceDB - db runByTraceIDGetter + traceGetter traceGetter } -func newCollectorDB(repository runByTraceIDGetter) (TraceDB, error) { +func newCollectorDB(repository traceGetter) (TraceDB, error) { return &OTLPTraceDB{ - db: repository, + traceGetter: repository, }, nil } @@ -44,17 +40,7 @@ func (tdb *OTLPTraceDB) GetEndpoints() string { } // GetTraceByID implements TraceDB -func (tdb *OTLPTraceDB) GetTraceByID(ctx context.Context, id string) (model.Trace, error) { - run, err := tdb.db.GetRunByTraceID(ctx, traces.DecodeTraceID(id)) - if err != nil && strings.Contains(err.Error(), "record not found") { - return model.Trace{}, connection.ErrTraceNotFound - } +func (tdb *OTLPTraceDB) GetTraceByID(ctx context.Context, id string) (traces.Trace, error) { + return tdb.traceGetter.Get(ctx, traces.DecodeTraceID(id)) - if run.Trace == nil { - return model.Trace{}, connection.ErrTraceNotFound - } - - return *run.Trace, nil } - -var _ TraceDB = &OTLPTraceDB{} diff --git a/server/tracedb/signalfxdb.go b/server/tracedb/signalfxdb.go index b08deafce4..5110962ce2 100644 --- a/server/tracedb/signalfxdb.go +++ b/server/tracedb/signalfxdb.go @@ -13,6 +13,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/tracedb/connection" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) @@ -68,22 +69,22 @@ func (db *signalfxDB) TestConnection(ctx context.Context) model.ConnectionResult return tester.TestConnection(ctx) } -func (db *signalfxDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (db *signalfxDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { timestamps, err := db.getSegmentsTimestamps(ctx, traceID) if err != nil { - return model.Trace{}, fmt.Errorf("coult not get trace segment timestamps: %w", err) + return traces.Trace{}, fmt.Errorf("coult not get trace segment timestamps: %w", err) } if len(timestamps) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } - traceSpans := make([]model.Span, 0) + traceSpans := make([]traces.Span, 0) for _, timestamp := range timestamps { segmentSpans, err := db.getSegmentSpans(ctx, traceID, timestamp) if err != nil { - return model.Trace{}, fmt.Errorf("could not get segment spans: %w", err) + return traces.Trace{}, fmt.Errorf("could not get segment spans: %w", err) } for _, signalFxSpan := range segmentSpans { @@ -93,10 +94,10 @@ func (db *signalfxDB) GetTraceByID(ctx context.Context, traceID string) (model.T } if len(traceSpans) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } - return model.NewTrace(traceID, traceSpans), nil + return traces.NewTrace(traceID, traceSpans), nil } func (db signalfxDB) getSegmentsTimestamps(ctx context.Context, traceID string) ([]int64, error) { @@ -167,8 +168,8 @@ func (db signalfxDB) getSegmentSpans(ctx context.Context, traceID string, timest return spans, nil } -func convertSignalFXSpan(in signalFXSpan) model.Span { - attributes := make(model.Attributes, 0) +func convertSignalFXSpan(in signalFXSpan) traces.Span { + attributes := make(traces.Attributes, 0) for name, value := range in.Tags { attributes[name] = value } @@ -177,15 +178,15 @@ func convertSignalFXSpan(in signalFXSpan) model.Span { attributes[name] = value } - attributes[model.TracetestMetadataFieldParentID] = in.ParentID - attributes[model.TracetestMetadataFieldKind] = attributes["span.kind"] + attributes[traces.TracetestMetadataFieldParentID] = in.ParentID + attributes[traces.TracetestMetadataFieldKind] = attributes["span.kind"] delete(attributes, "span.kind") spanID, _ := trace.SpanIDFromHex(in.SpanID) startTime, _ := time.Parse(time.RFC3339, in.StartTime) endTime := startTime.Add(time.Duration(in.Duration) * time.Microsecond) - return model.Span{ + return traces.Span{ ID: spanID, Name: in.Name, StartTime: startTime, diff --git a/server/tracedb/tempodb.go b/server/tracedb/tempodb.go index ee6d0a3b87..5f2f08cb7d 100644 --- a/server/tracedb/tempodb.go +++ b/server/tracedb/tempodb.go @@ -72,7 +72,7 @@ func (ttd *tempoTraceDB) Ready() bool { return ttd.dataSource.Ready() } -func (ttd *tempoTraceDB) GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) { +func (ttd *tempoTraceDB) GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) { trace, err := ttd.dataSource.GetTraceByID(ctx, traceID) return trace, err } @@ -81,27 +81,27 @@ func (ttd *tempoTraceDB) Close() error { return ttd.dataSource.Close() } -func grpcGetTraceByID(ctx context.Context, traceID string, conn *grpc.ClientConn) (model.Trace, error) { +func grpcGetTraceByID(ctx context.Context, traceID string, conn *grpc.ClientConn) (traces.Trace, error) { query := tempopb.NewQuerierClient(conn) trID, err := trace.TraceIDFromHex(traceID) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } resp, err := query.FindTraceByID(ctx, &tempopb.TraceByIDRequest{ TraceID: []byte(trID[:]), }) if err != nil { - return model.Trace{}, handleError(err) + return traces.Trace{}, handleError(err) } if resp.Trace == nil { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } if len(resp.Trace.Batches) == 0 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } trace := &v1.TracesData{ @@ -115,19 +115,19 @@ type HttpTempoTraceByIDResponse struct { Batches []*traces.HttpResourceSpans `json:"batches"` } -func httpGetTraceByID(ctx context.Context, traceID string, client *datasource.HttpClient) (model.Trace, error) { +func httpGetTraceByID(ctx context.Context, traceID string, client *datasource.HttpClient) (traces.Trace, error) { trID, err := trace.TraceIDFromHex(traceID) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } resp, err := client.Request(ctx, fmt.Sprintf("/api/traces/%s", trID), http.MethodGet, "") if err != nil { - return model.Trace{}, handleError(err) + return traces.Trace{}, handleError(err) } if resp.StatusCode == 404 { - return model.Trace{}, connection.ErrTraceNotFound + return traces.Trace{}, connection.ErrTraceNotFound } var body []byte @@ -138,13 +138,13 @@ func httpGetTraceByID(ctx context.Context, traceID string, client *datasource.Ht } if resp.StatusCode == 401 { - return model.Trace{}, fmt.Errorf("tempo err: %w %s", errors.New("authentication handshake failed"), string(body)) + return traces.Trace{}, fmt.Errorf("tempo err: %w %s", errors.New("authentication handshake failed"), string(body)) } var trace HttpTempoTraceByIDResponse err = json.Unmarshal(body, &trace) if err != nil { - return model.Trace{}, err + return traces.Trace{}, err } return traces.FromHttpOtelResourceSpans(trace.Batches), nil diff --git a/server/tracedb/tracedb.go b/server/tracedb/tracedb.go index a125785247..21e51c0b39 100644 --- a/server/tracedb/tracedb.go +++ b/server/tracedb/tracedb.go @@ -7,6 +7,7 @@ import ( "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" + "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) @@ -17,7 +18,7 @@ type TraceDB interface { Ready() bool ShouldRetry() bool GetTraceID() trace.TraceID - GetTraceByID(ctx context.Context, traceID string) (model.Trace, error) + GetTraceByID(ctx context.Context, traceID string) (traces.Trace, error) Close() error GetEndpoints() string } @@ -29,8 +30,8 @@ type TestableTraceDB interface { type noopTraceDB struct{} -func (db *noopTraceDB) GetTraceByID(ctx context.Context, traceID string) (t model.Trace, err error) { - return model.Trace{}, nil +func (db *noopTraceDB) GetTraceByID(ctx context.Context, traceID string) (t traces.Trace, err error) { + return traces.Trace{}, nil } func (db *noopTraceDB) GetTraceID() trace.TraceID { @@ -46,10 +47,10 @@ func (db *noopTraceDB) TestConnection(ctx context.Context) model.ConnectionResul } type traceDBFactory struct { - runRepository runByTraceIDGetter + runRepository traceGetter } -func Factory(runRepository runByTraceIDGetter) func(ds datastore.DataStore) (TraceDB, error) { +func Factory(runRepository traceGetter) func(ds datastore.DataStore) (TraceDB, error) { f := traceDBFactory{ runRepository: runRepository, } diff --git a/server/traces/otel_converter.go b/server/traces/otel_converter.go index 57f1f55387..12b81d32bc 100644 --- a/server/traces/otel_converter.go +++ b/server/traces/otel_converter.go @@ -6,17 +6,16 @@ import ( "math" "time" - "github.com/kubeshop/tracetest/server/model" "go.opentelemetry.io/otel/trace" v11 "go.opentelemetry.io/proto/otlp/common/v1" v1 "go.opentelemetry.io/proto/otlp/trace/v1" ) -func FromOtel(input *v1.TracesData) model.Trace { +func FromOtel(input *v1.TracesData) Trace { return FromOtelResourceSpans(input.ResourceSpans) } -func FromOtelResourceSpans(resourceSpans []*v1.ResourceSpans) model.Trace { +func FromOtelResourceSpans(resourceSpans []*v1.ResourceSpans) Trace { flattenSpans := make([]*v1.Span, 0) for _, resource := range resourceSpans { for _, scopeSpans := range resource.ScopeSpans { @@ -25,18 +24,18 @@ func FromOtelResourceSpans(resourceSpans []*v1.ResourceSpans) model.Trace { } traceID := "" - spans := make([]model.Span, 0) + spans := make([]Span, 0) for _, span := range flattenSpans { newSpan := ConvertOtelSpanIntoSpan(span) traceID = hex.EncodeToString(span.TraceId) spans = append(spans, *newSpan) } - return model.NewTrace(traceID, spans) + return NewTrace(traceID, spans) } -func ConvertOtelSpanIntoSpan(span *v1.Span) *model.Span { - attributes := make(model.Attributes, 0) +func ConvertOtelSpanIntoSpan(span *v1.Span) *Span { + attributes := make(Attributes, 0) for _, attribute := range span.Attributes { attributes[attribute.Key] = getAttributeValue(attribute.Value) } @@ -60,8 +59,8 @@ func ConvertOtelSpanIntoSpan(span *v1.Span) *model.Span { } spanID := createSpanID(span.SpanId) - attributes[model.TracetestMetadataFieldParentID] = createSpanID(span.ParentSpanId).String() - return &model.Span{ + attributes[TracetestMetadataFieldParentID] = createSpanID(span.ParentSpanId).String() + return &Span{ ID: spanID, Name: span.Name, Kind: spanKind(span), @@ -70,15 +69,15 @@ func ConvertOtelSpanIntoSpan(span *v1.Span) *model.Span { Parent: nil, Events: extractEvents(span), Status: spanStatus, - Children: make([]*model.Span, 0), + Children: make([]*Span, 0), Attributes: attributes, } } -func extractEvents(v1 *v1.Span) []model.SpanEvent { - output := make([]model.SpanEvent, 0, len(v1.Events)) +func extractEvents(v1 *v1.Span) []SpanEvent { + output := make([]SpanEvent, 0, len(v1.Events)) for _, v1Event := range v1.Events { - attributes := make(model.Attributes, 0) + attributes := make(Attributes, 0) for _, attribute := range v1Event.Attributes { attributes[attribute.Key] = getAttributeValue(attribute.Value) } @@ -88,7 +87,7 @@ func extractEvents(v1 *v1.Span) []model.SpanEvent { timestamp = time.Unix(0, int64(v1Event.GetTimeUnixNano())) } - output = append(output, model.SpanEvent{ + output = append(output, SpanEvent{ Name: v1Event.Name, Timestamp: timestamp, Attributes: attributes, @@ -98,20 +97,20 @@ func extractEvents(v1 *v1.Span) []model.SpanEvent { return output } -func spanKind(span *v1.Span) model.SpanKind { +func spanKind(span *v1.Span) SpanKind { switch span.Kind { case v1.Span_SPAN_KIND_CLIENT: - return model.SpanKindClient + return SpanKindClient case v1.Span_SPAN_KIND_SERVER: - return model.SpanKindServer + return SpanKindServer case v1.Span_SPAN_KIND_INTERNAL: - return model.SpanKindInternal + return SpanKindInternal case v1.Span_SPAN_KIND_PRODUCER: - return model.SpanKindProducer + return SpanKindProducer case v1.Span_SPAN_KIND_CONSUMER: - return model.SpanKindConsumer + return SpanKindConsumer default: - return model.SpanKindUnespecified + return SpanKindUnespecified } } diff --git a/server/traces/otel_http_converter.go b/server/traces/otel_http_converter.go index c1e5ecf457..2017629c1d 100644 --- a/server/traces/otel_http_converter.go +++ b/server/traces/otel_http_converter.go @@ -7,7 +7,6 @@ import ( "strconv" "time" - "github.com/kubeshop/tracetest/server/model" v11 "go.opentelemetry.io/proto/otlp/common/v1" v1 "go.opentelemetry.io/proto/otlp/trace/v1" ) @@ -50,7 +49,7 @@ type httpSpanAttribute struct { Value map[string]interface{} `json:"value"` } -func FromHttpOtelResourceSpans(resourceSpans []*HttpResourceSpans) model.Trace { +func FromHttpOtelResourceSpans(resourceSpans []*HttpResourceSpans) Trace { flattenSpans := make([]*httpSpan, 0) for _, resource := range resourceSpans { for _, scopeSpans := range resource.ScopeSpans { @@ -63,18 +62,18 @@ func FromHttpOtelResourceSpans(resourceSpans []*HttpResourceSpans) model.Trace { } traceID := "" - spans := make([]model.Span, 0) + spans := make([]Span, 0) for _, span := range flattenSpans { newSpan := convertHttpOtelSpanIntoSpan(span) traceID = hex.EncodeToString(span.TraceId) spans = append(spans, *newSpan) } - return model.NewTrace(traceID, spans) + return NewTrace(traceID, spans) } -func convertHttpOtelSpanIntoSpan(span *httpSpan) *model.Span { - attributes := make(model.Attributes, 0) +func convertHttpOtelSpanIntoSpan(span *httpSpan) *Span { + attributes := make(Attributes, 0) for _, attribute := range span.Attributes { attributes[attribute.Key] = getHttpAttributeValue(attribute.Value) } @@ -92,24 +91,24 @@ func convertHttpOtelSpanIntoSpan(span *httpSpan) *model.Span { } spanID := createSpanID([]byte(span.SpanId)) - attributes[model.TracetestMetadataFieldParentID] = createSpanID([]byte(span.ParentSpanId)).String() + attributes[TracetestMetadataFieldParentID] = createSpanID([]byte(span.ParentSpanId)).String() - return &model.Span{ + return &Span{ ID: spanID, Name: span.Name, StartTime: startTime, EndTime: endTime, Parent: nil, - Children: make([]*model.Span, 0), + Children: make([]*Span, 0), Attributes: attributes, Events: extractEventsFromHttpSpan(span), } } -func extractEventsFromHttpSpan(span *httpSpan) []model.SpanEvent { - output := make([]model.SpanEvent, 0, len(span.Events)) +func extractEventsFromHttpSpan(span *httpSpan) []SpanEvent { + output := make([]SpanEvent, 0, len(span.Events)) for _, event := range span.Events { - attributes := make(model.Attributes, 0) + attributes := make(Attributes, 0) for _, attribute := range event.Attributes { attributes[attribute.Key] = getHttpAttributeValue(attribute.Value) } @@ -120,7 +119,7 @@ func extractEventsFromHttpSpan(span *httpSpan) []model.SpanEvent { timestamp = time.Unix(0, timestampNs) } - output = append(output, model.SpanEvent{ + output = append(output, SpanEvent{ Name: event.Name, Timestamp: timestamp, Attributes: attributes, diff --git a/server/model/spans.go b/server/traces/spans.go similarity index 99% rename from server/model/spans.go rename to server/traces/spans.go index 5ac28ecbd6..57515d0ef6 100644 --- a/server/model/spans.go +++ b/server/traces/spans.go @@ -1,4 +1,4 @@ -package model +package traces import ( "encoding/json" diff --git a/server/model/traces.go b/server/traces/traces.go similarity index 99% rename from server/model/traces.go rename to server/traces/traces.go index 47b0d349d9..33e5727cb8 100644 --- a/server/model/traces.go +++ b/server/traces/traces.go @@ -1,4 +1,4 @@ -package model +package traces import ( "encoding/json" diff --git a/server/traces/traces_test.go b/server/traces/traces_test.go index 9f80ce4ae5..69ca73ef12 100644 --- a/server/traces/traces_test.go +++ b/server/traces/traces_test.go @@ -6,13 +6,253 @@ import ( "testing" "time" - "github.com/kubeshop/tracetest/server/model" "github.com/kubeshop/tracetest/server/pkg/id" + "github.com/kubeshop/tracetest/server/traces" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" + v1 "go.opentelemetry.io/proto/otlp/trace/v1" ) +func TestTraces(t *testing.T) { + rootSpan := newSpan("Root") + childSpan1 := newSpan("child 1", withParent(&rootSpan)) + childSpan2 := newSpan("child 2", withParent(&rootSpan)) + grandchildSpan := newSpan("grandchild", withParent(&childSpan2)) + + spans := []traces.Span{rootSpan, childSpan1, childSpan2, grandchildSpan} + trace := traces.NewTrace("trace", spans) + + assert.Len(t, trace.Flat, 4) + assert.Equal(t, "Root", trace.RootSpan.Name) + assert.Equal(t, "child 1", trace.RootSpan.Children[0].Name) + assert.Equal(t, "child 2", trace.RootSpan.Children[1].Name) + assert.Equal(t, "grandchild", trace.RootSpan.Children[1].Children[0].Name) +} + +func TestTraceWithMultipleRoots(t *testing.T) { + root1 := newSpan("Root 1") + root1Child := newSpan("Child from root 1", withParent(&root1)) + root2 := newSpan("Root 2") + root2Child := newSpan("Child from root 2", withParent(&root2)) + root3 := newSpan("Root 3") + root3Child := newSpan("Child from root 3", withParent(&root3)) + + spans := []traces.Span{root1, root1Child, root2, root2Child, root3, root3Child} + trace := traces.NewTrace("trace", spans) + + // agreggate root + 3 roots + 3 child + assert.Len(t, trace.Flat, 7) + assert.Equal(t, traces.TemporaryRootSpanName, trace.RootSpan.Name) + assert.Equal(t, "Root 1", trace.RootSpan.Children[0].Name) + assert.Equal(t, "Root 2", trace.RootSpan.Children[1].Name) + assert.Equal(t, "Root 3", trace.RootSpan.Children[2].Name) + assert.Equal(t, "Child from root 1", trace.RootSpan.Children[0].Children[0].Name) + assert.Equal(t, "Child from root 2", trace.RootSpan.Children[1].Children[0].Name) + assert.Equal(t, "Child from root 3", trace.RootSpan.Children[2].Children[0].Name) +} + +func TestTraceWithMultipleRootsFromOtel(t *testing.T) { + root1 := newOtelSpan("Root 1", nil) + root1Child := newOtelSpan("Child from root 1", root1) + root2 := newOtelSpan("Root 2", nil) + root2Child := newOtelSpan("Child from root 2", root2) + root3 := newOtelSpan("Root 3", nil) + root3Child := newOtelSpan("Child from root 3", root3) + + tracesData := &v1.TracesData{ + ResourceSpans: []*v1.ResourceSpans{ + { + ScopeSpans: []*v1.ScopeSpans{ + { + Spans: []*v1.Span{root1, root1Child, root2, root2Child, root3, root3Child}, + }, + }, + }, + }, + } + + trace := traces.FromOtel(tracesData) + + // agreggate root + 3 roots + 3 child + assert.Len(t, trace.Flat, 7) + assert.Equal(t, traces.TemporaryRootSpanName, trace.RootSpan.Name) + assert.Equal(t, "Root 1", trace.RootSpan.Children[0].Name) + assert.Equal(t, "Root 2", trace.RootSpan.Children[1].Name) + assert.Equal(t, "Root 3", trace.RootSpan.Children[2].Name) + assert.Equal(t, "Child from root 1", trace.RootSpan.Children[0].Children[0].Name) + assert.Equal(t, "Child from root 2", trace.RootSpan.Children[1].Children[0].Name) + assert.Equal(t, "Child from root 3", trace.RootSpan.Children[2].Children[0].Name) +} + +func TestInjectingNewRootWhenSingleRoot(t *testing.T) { + rootSpan := newSpan("Root") + childSpan1 := newSpan("child 1", withParent(&rootSpan)) + childSpan2 := newSpan("child 2", withParent(&rootSpan)) + grandchildSpan := newSpan("grandchild", withParent(&childSpan2)) + + spans := []traces.Span{rootSpan, childSpan1, childSpan2, grandchildSpan} + trace := traces.NewTrace("trace", spans) + + newRoot := newSpan("new Root") + newTrace := trace.InsertRootSpan(newRoot) + + assert.Len(t, newTrace.Flat, 5) + assert.Equal(t, "new Root", newTrace.RootSpan.Name) + assert.Len(t, newTrace.RootSpan.Children, 1) + assert.Equal(t, "Root", newTrace.RootSpan.Children[0].Name) +} + +func TestInjectingNewRootWhenMultipleRoots(t *testing.T) { + root1 := newSpan("Root 1") + root1Child := newSpan("Child from root 1", withParent(&root1)) + root2 := newSpan("Root 2") + root2Child := newSpan("Child from root 2", withParent(&root2)) + root3 := newSpan("Root 3") + root3Child := newSpan("Child from root 3", withParent(&root3)) + + spans := []traces.Span{root1, root1Child, root2, root2Child, root3, root3Child} + trace := traces.NewTrace("trace", spans) + + for _, oldRoot := range trace.RootSpan.Children { + require.NotNil(t, oldRoot.Parent) + } + + newRoot := newSpan("new Root") + newTrace := trace.InsertRootSpan(newRoot) + + assert.Len(t, newTrace.Flat, 7) + assert.Equal(t, "new Root", newTrace.RootSpan.Name) + assert.Len(t, newTrace.RootSpan.Children, 3) + assert.Equal(t, "Root 1", newTrace.RootSpan.Children[0].Name) + assert.Equal(t, "Root 2", newTrace.RootSpan.Children[1].Name) + assert.Equal(t, "Root 3", newTrace.RootSpan.Children[2].Name) + + for _, oldRoot := range trace.RootSpan.Children { + require.NotNil(t, oldRoot.Parent) + assert.Equal(t, newRoot.ID.String(), oldRoot.Parent.ID.String()) + } +} + +func TestNoTemporaryRootIfTracetestRootExists(t *testing.T) { + root1 := newSpan("Root 1") + root1Child := newSpan("Child from root 1", withParent(&root1)) + root2 := newSpan(traces.TriggerSpanName) + root2Child := newSpan("Child from root 2", withParent(&root2)) + root3 := newSpan("Root 3") + root3Child := newSpan("Child from root 3", withParent(&root3)) + + spans := []traces.Span{root1, root1Child, root2, root2Child, root3, root3Child} + trace := traces.NewTrace("trace", spans) + + assert.Equal(t, root2.ID, trace.RootSpan.ID) + assert.Equal(t, root2.Name, trace.RootSpan.Name) +} + +func TestNoTemporaryRootIfATemporaryRootExists(t *testing.T) { + root1 := newSpan("Root 1") + root1Child := newSpan("Child from root 1", withParent(&root1)) + root2 := newSpan(traces.TemporaryRootSpanName) + root2Child := newSpan("Child from root 2", withParent(&root2)) + root3 := newSpan("Root 3") + root3Child := newSpan("Child from root 3", withParent(&root3)) + + spans := []traces.Span{root1, root1Child, root2, root2Child, root3, root3Child} + trace := traces.NewTrace("trace", spans) + + assert.Equal(t, root2.ID, trace.RootSpan.ID) + assert.Equal(t, root2.Name, trace.RootSpan.Name) +} + +func TestTriggerSpanShouldBeRootWhenTemporaryRootExistsToo(t *testing.T) { + root1 := newSpan(traces.TriggerSpanName) + root1Child := newSpan("Child from root 1", withParent(&root1)) + root2 := newSpan(traces.TemporaryRootSpanName) + root2Child := newSpan("Child from root 2", withParent(&root2)) + root3 := newSpan("Root 3") + root3Child := newSpan("Child from root 3", withParent(&root3)) + + spans := []traces.Span{root1, root1Child, root2, root2Child, root3, root3Child} + trace := traces.NewTrace("trace", spans) + + assert.Equal(t, root1.ID, trace.RootSpan.ID) + assert.Equal(t, root1.Name, trace.RootSpan.Name) +} + +func TestEventsAreInjectedIntoAttributes(t *testing.T) { + rootSpan := newSpan("Root", withEvents([]traces.SpanEvent{ + {Name: "event 1", Attributes: traces.Attributes{"attribute1": "value"}}, + {Name: "event 2", Attributes: traces.Attributes{"attribute2": "value"}}, + })) + childSpan1 := newSpan("child 1", withParent(&rootSpan)) + childSpan2 := newSpan("child 2", withParent(&rootSpan)) + grandchildSpan := newSpan("grandchild", withParent(&childSpan2)) + + spans := []traces.Span{rootSpan, childSpan1, childSpan2, grandchildSpan} + trace := traces.NewTrace("trace", spans) + + require.NotEmpty(t, trace.RootSpan.Attributes["span.events"]) + + events := []traces.SpanEvent{} + err := json.Unmarshal([]byte(trace.RootSpan.Attributes["span.events"]), &events) + require.NoError(t, err) + + assert.Equal(t, "event 1", events[0].Name) + assert.Equal(t, "value", events[0].Attributes["attribute1"]) + assert.Equal(t, "event 2", events[1].Name) + assert.Equal(t, "value", events[1].Attributes["attribute2"]) +} + +type option func(*traces.Span) + +func withParent(parent *traces.Span) option { + return func(s *traces.Span) { + s.Parent = parent + } +} + +func withEvents(events []traces.SpanEvent) option { + return func(s *traces.Span) { + s.Events = events + } +} + +func newSpan(name string, options ...option) traces.Span { + span := traces.Span{ + ID: id.NewRandGenerator().SpanID(), + Name: name, + Attributes: make(traces.Attributes), + StartTime: time.Now(), + EndTime: time.Now().Add(1 * time.Second), + } + + for _, option := range options { + option(&span) + } + + if span.Parent != nil { + span.Attributes[traces.TracetestMetadataFieldParentID] = span.Parent.ID.String() + } + + return span +} + +func newOtelSpan(name string, parent *v1.Span) *v1.Span { + id := id.NewRandGenerator().SpanID() + var parentId []byte = nil + if parent != nil { + parentId = parent.SpanId + } + + return &v1.Span{ + SpanId: id[:], + Name: name, + ParentSpanId: parentId, + StartTimeUnixNano: uint64(time.Now().UnixNano()), + EndTimeUnixNano: uint64(time.Now().Add(1 * time.Second).UnixNano()), + } +} + func TestJSONEncoding(t *testing.T) { rootSpan := createSpan("root") @@ -24,7 +264,7 @@ func TestJSONEncoding(t *testing.T) { subSpan2.Parent = rootSpan subSubSpan1.Parent = subSpan1 - flat := map[trace.SpanID]*model.Span{ + flat := map[trace.SpanID]*traces.Span{ // We copy those spans so they don't have children injected into them // the flat structure shouldn't have children. rootSpan.ID: copySpan(rootSpan), @@ -33,11 +273,11 @@ func TestJSONEncoding(t *testing.T) { subSpan2.ID: copySpan(subSpan2), } - rootSpan.Children = []*model.Span{subSpan1, subSpan2} - subSpan1.Children = []*model.Span{subSubSpan1} + rootSpan.Children = []*traces.Span{subSpan1, subSpan2} + subSpan1.Children = []*traces.Span{subSubSpan1} tid := id.NewRandGenerator().TraceID() - trace := model.Trace{ + trace := traces.Trace{ ID: tid, RootSpan: *rootSpan, Flat: flat, @@ -115,7 +355,7 @@ func TestJSONEncoding(t *testing.T) { }) t.Run("decode", func(t *testing.T) { - var actual model.Trace + var actual traces.Trace err := json.Unmarshal([]byte(jsonEncoded), &actual) require.NoError(t, err) @@ -130,18 +370,18 @@ func TestJSONEncoding(t *testing.T) { }) } -func copySpan(span *model.Span) *model.Span { +func copySpan(span *traces.Span) *traces.Span { newSpan := *span return &newSpan } -func createSpan(name string) *model.Span { - return &model.Span{ +func createSpan(name string) *traces.Span { + return &traces.Span{ ID: id.NewRandGenerator().SpanID(), Name: name, StartTime: time.Date(2021, 11, 24, 14, 05, 12, 0, time.UTC), EndTime: time.Date(2021, 11, 24, 14, 05, 17, 0, time.UTC), - Attributes: model.Attributes{ + Attributes: traces.Attributes{ "service.name": name, }, Children: nil, @@ -156,13 +396,13 @@ func getTime(n int) time.Time { func TestSort(t *testing.T) { randGenerator := id.NewRandGenerator() - trace := model.Trace{ + trace := traces.Trace{ ID: randGenerator.TraceID(), - RootSpan: model.Span{ + RootSpan: traces.Span{ Name: "root", StartTime: getTime(0), - Attributes: model.Attributes{}, - Children: []*model.Span{ + Attributes: traces.Attributes{}, + Children: []*traces.Span{ { Name: "child 2", StartTime: getTime(2), @@ -174,7 +414,7 @@ func TestSort(t *testing.T) { { Name: "child 1", StartTime: getTime(1), - Children: []*model.Span{ + Children: []*traces.Span{ { Name: "grandchild 1", StartTime: getTime(2), @@ -191,40 +431,40 @@ func TestSort(t *testing.T) { sortedTrace := trace.Sort() - expectedTrace := model.Trace{ + expectedTrace := traces.Trace{ ID: randGenerator.TraceID(), - RootSpan: model.Span{ + RootSpan: traces.Span{ Name: "root", StartTime: getTime(0), - Attributes: model.Attributes{}, - Children: []*model.Span{ + Attributes: traces.Attributes{}, + Children: []*traces.Span{ { Name: "child 1", StartTime: getTime(1), - Children: []*model.Span{ + Children: []*traces.Span{ { Name: "grandchild 1", StartTime: getTime(2), - Children: make([]*model.Span, 0), + Children: make([]*traces.Span, 0), }, { Name: "grandchild 2", StartTime: getTime(3), - Children: make([]*model.Span, 0), + Children: make([]*traces.Span, 0), }, }, }, { Name: "child 2", StartTime: getTime(2), - Children: make([]*model.Span, 0), + Children: make([]*traces.Span, 0), }, { Name: "child 3", StartTime: getTime(3), - Children: make([]*model.Span, 0), + Children: make([]*traces.Span, 0), }, }, }, From f9a1d9fa34e0041a75378bd8e01924dcfc84a6a7 Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 20:14:12 -0300 Subject: [PATCH 3/9] make it work --- run.sh | 1 + server/app/app.go | 16 ++- server/app/test_pipeline.go | 5 +- server/executor/default_poller_executor.go | 14 ++- server/executor/runner.go | 4 +- server/executor/runner_test.go | 20 +++- server/migrations/30_add_traces.down.sql | 5 + server/migrations/30_add_traces.up.sql | 10 ++ server/otlp/ingester.go | 63 ++++++++-- server/tracedb/otlp.go | 10 +- server/tracedb/tracedb.go | 10 +- server/traces/otel_converter.go | 8 +- server/traces/{spans.go => span_entitiess.go} | 0 .../traces/{traces.go => trace_entities.go} | 0 server/traces/trace_repository.go | 110 ++++++++++++++++++ 15 files changed, 243 insertions(+), 33 deletions(-) create mode 100644 server/migrations/30_add_traces.down.sql create mode 100644 server/migrations/30_add_traces.up.sql rename server/traces/{spans.go => span_entitiess.go} (100%) rename server/traces/{traces.go => trace_entities.go} (100%) create mode 100644 server/traces/trace_repository.go diff --git a/run.sh b/run.sh index 325ce30d2c..689b0aaf1c 100755 --- a/run.sh +++ b/run.sh @@ -13,6 +13,7 @@ help_message() { restart() { docker compose $opts kill tracetest docker compose $opts up -d tracetest + docker compose $opts restart otel-collector } logs() { diff --git a/server/app/app.go b/server/app/app.go index 93fc542dc3..bee6bb7e48 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -201,12 +201,15 @@ func (app *App) Start(opts ...appOption) error { testRepo := test.NewRepository(db) runRepo := test.NewRunRepository(db) testRunnerRepo := testrunner.NewRepository(db) + tracesRepo := traces.NewTraceRepository(db) transactionsRepository := transaction.NewRepository(db, testRepo) transactionRunRepository := transaction.NewRunRepository(db, runRepo) + tracedbFactory := tracedb.Factory(tracesRepo) + eventEmitter := executor.NewEventEmitter(testDB, subscriptionManager) - registerOtlpServer(app, runRepo, eventEmitter, dataStoreRepo) + registerOtlpServer(app, tracesRepo, runRepo, eventEmitter, dataStoreRepo) testPipeline := buildTestPipeline( pollingProfileRepo, @@ -219,6 +222,7 @@ func (app *App) Start(opts ...appOption) error { tracer, subscriptionManager, triggerRegistry, + tracedbFactory, ) testPipeline.Start() app.registerStopFn(func() { @@ -256,6 +260,7 @@ func (app *App) Start(opts ...appOption) error { testRepo, runRepo, variableSetRepo, + tracedbFactory, ) registerWSHandler(router, mappers, subscriptionManager) @@ -350,8 +355,8 @@ func registerSPAHandler(router *mux.Router, cfg httpServerConfig, analyticsEnabl ) } -func registerOtlpServer(app *App, runRepository test.RunRepository, eventEmitter executor.EventEmitter, dsRepo *datastore.Repository) { - ingester := otlp.NewIngester(runRepository, eventEmitter, dsRepo) +func registerOtlpServer(app *App, tracesRepo *traces.TraceRepository, runRepository test.RunRepository, eventEmitter executor.EventEmitter, dsRepo *datastore.Repository) { + ingester := otlp.NewIngester(tracesRepo, runRepository, eventEmitter, dsRepo) grpcOtlpServer := otlp.NewGrpcServer(":4317", ingester) httpOtlpServer := otlp.NewHttpServer(":4318", ingester) go grpcOtlpServer.Start() @@ -510,6 +515,7 @@ func controller( testRepo test.Repository, testRunRepo test.RunRepository, variablesetRepo *variableset.Repository, + tracedbFactory tracedb.FactoryFunc, ) (*mux.Router, mappings.Mappings) { mappers := mappings.New(tracesConversionConfig(), comparator.DefaultRegistry()) @@ -527,6 +533,7 @@ func controller( testRepo, testRunRepo, variablesetRepo, + tracedbFactory, mappers, )) @@ -548,6 +555,7 @@ func httpRouter( testRepo test.Repository, testRunRepo test.RunRepository, variableSetRepo *variableset.Repository, + tracedbFactory tracedb.FactoryFunc, mappers mappings.Mappings, ) openapi.Router { @@ -564,7 +572,7 @@ func httpRouter( testRunRepo, variableSetRepo, - tracedb.Factory(testRunRepo), + tracedbFactory, mappers, Version, ) diff --git a/server/app/test_pipeline.go b/server/app/test_pipeline.go index feeb8a6b04..14e1029a87 100644 --- a/server/app/test_pipeline.go +++ b/server/app/test_pipeline.go @@ -25,6 +25,7 @@ func buildTestPipeline( tracer trace.Tracer, subscriptionManager *subscription.Manager, triggerRegistry *trigger.Registry, + tracedbFactory tracedb.FactoryFunc, ) *executor.TestPipeline { eventEmitter := executor.NewEventEmitter(treRepo, subscriptionManager) @@ -51,7 +52,7 @@ func buildTestPipeline( executor.NewPollerExecutor( tracer, execTestUpdater, - tracedb.Factory(runRepo), + tracedbFactory, dsRepo, eventEmitter, ), @@ -70,7 +71,7 @@ func buildTestPipeline( execTestUpdater, tracer, subscriptionManager, - tracedb.Factory(runRepo), + tracedbFactory, dsRepo, eventEmitter, ) diff --git a/server/executor/default_poller_executor.go b/server/executor/default_poller_executor.go index 66937cffc8..f7172b1dfa 100644 --- a/server/executor/default_poller_executor.go +++ b/server/executor/default_poller_executor.go @@ -16,11 +16,9 @@ import ( "go.opentelemetry.io/otel/trace" ) -type traceDBFactoryFn func(ds datastore.DataStore) (tracedb.TraceDB, error) - type DefaultPollerExecutor struct { updater RunUpdater - newTraceDBFn traceDBFactoryFn + newTraceDBFn tracedb.FactoryFunc dsRepo resourcemanager.Current[datastore.DataStore] eventEmitter EventEmitter } @@ -65,7 +63,7 @@ func (pe InstrumentedPollerExecutor) ExecuteRequest(ctx context.Context, job *Jo func NewPollerExecutor( tracer trace.Tracer, updater RunUpdater, - newTraceDBFn traceDBFactoryFn, + newTraceDBFn tracedb.FactoryFunc, dsRepo resourcemanager.Current[datastore.DataStore], eventEmitter EventEmitter, ) *InstrumentedPollerExecutor { @@ -119,10 +117,14 @@ func (pe DefaultPollerExecutor) ExecuteRequest(ctx context.Context, job *Job) (P return PollResult{}, err } - trace.ID = job.Run.TraceID done, reason := pe.donePollingTraces(job, traceDB, trace) // we need both values to be different to check for done, but after we want to have an updated job job.Run.Trace = &trace + err = pe.updater.Update(ctx, job.Run) + if err != nil { + log.Printf("[PollerExecutor] Test %s Run %d: Update error: %s", job.Test.ID, job.Run.ID, err.Error()) + return PollResult{}, err + } if !done { pe.emit(ctx, job, events.TracePollingIterationInfo(job.Test.ID, job.Run.ID, len(job.Run.Trace.Flat), job.EnqueueCount(), false, reason)) @@ -205,7 +207,7 @@ func (pe DefaultPollerExecutor) donePollingTraces(job *Job, traceDB tracedb.Trac maxTracePollRetry := job.PollingProfile.Periodic.MaxTracePollRetry() // we're done if we have the same amount of spans after polling or `maxTracePollRetry` times log.Printf("[PollerExecutor] Test %s Run %d: Job count %d, max retries: %d", job.Test.ID, job.Run.ID, job.EnqueueCount(), maxTracePollRetry) - if job.EnqueueCount() == maxTracePollRetry { + if job.EnqueueCount() >= maxTracePollRetry { return true, fmt.Sprintf("Hit MaxRetry of %d", maxTracePollRetry) } diff --git a/server/executor/runner.go b/server/executor/runner.go index 528588ba42..60f5b1c0f8 100644 --- a/server/executor/runner.go +++ b/server/executor/runner.go @@ -37,7 +37,7 @@ func NewPersistentRunner( updater RunUpdater, tracer trace.Tracer, subscriptionManager *subscription.Manager, - newTraceDBFn traceDBFactoryFn, + newTraceDBFn tracedb.FactoryFunc, dsRepo currentDataStoreGetter, eventEmitter EventEmitter, ) *persistentRunner { @@ -61,7 +61,7 @@ type persistentRunner struct { updater RunUpdater tracer trace.Tracer subscriptionManager *subscription.Manager - newTraceDBFn traceDBFactoryFn + newTraceDBFn tracedb.FactoryFunc dsRepo currentDataStoreGetter eventEmitter EventEmitter outputQueue Enqueuer diff --git a/server/executor/runner_test.go b/server/executor/runner_test.go index 08291a7331..f16b5d0bbf 100644 --- a/server/executor/runner_test.go +++ b/server/executor/runner_test.go @@ -18,6 +18,7 @@ import ( "github.com/kubeshop/tracetest/server/test/trigger" "github.com/kubeshop/tracetest/server/testdb" "github.com/kubeshop/tracetest/server/tracedb" + "github.com/kubeshop/tracetest/server/traces" "github.com/kubeshop/tracetest/server/tracing" "github.com/kubeshop/tracetest/server/variableset" "github.com/stretchr/testify/assert" @@ -170,6 +171,9 @@ func runnerSetup(t *testing.T) runnerFixture { processorMock := new(mockProcessor) processorMock.Test(t) + tracesMock := new(mockTraces) + tracesMock.Test(t) + sm := subscription.NewManager() tracer, _ := tracing.NewTracer(context.Background(), config.Must(config.New())) eventEmitter := executor.NewEventEmitter(getTestRunEventRepositoryMock(t, false), sm) @@ -182,7 +186,7 @@ func runnerSetup(t *testing.T) runnerFixture { executor.NewDBUpdater(runsMock), tracer, sm, - tracedb.Factory(runsMock), + tracedb.Factory(tracesMock), dsMock, eventEmitter, ) @@ -220,6 +224,20 @@ func runnerSetup(t *testing.T) runnerFixture { } } +type mockTraces struct { + mock.Mock +} + +func (r *mockTraces) Get(ctx context.Context, id trace.TraceID) (traces.Trace, error) { + args := r.Called(id) + return args.Get(0).(traces.Trace), args.Error(1) +} + +func (r *mockTraces) SaveTrace(ctx context.Context, trace *traces.Trace) error { + args := r.Called(trace) + return args.Error(0) +} + type mockProcessor struct { mock.Mock } diff --git a/server/migrations/30_add_traces.down.sql b/server/migrations/30_add_traces.down.sql new file mode 100644 index 0000000000..16fb0ae256 --- /dev/null +++ b/server/migrations/30_add_traces.down.sql @@ -0,0 +1,5 @@ +BEGIN; + +DROP TABLE "otlp_traces"; + +COMMIT; diff --git a/server/migrations/30_add_traces.up.sql b/server/migrations/30_add_traces.up.sql new file mode 100644 index 0000000000..d05237b5d1 --- /dev/null +++ b/server/migrations/30_add_traces.up.sql @@ -0,0 +1,10 @@ +BEGIN; + +CREATE TABLE "otlp_traces" ( + "trace_id" varchar not null primary key, + "tenant_id" uuid, + "trace" JSONB, + "created_at" timestamp NOT NULL DEFAULT NOW() +); + +COMMIT; diff --git a/server/otlp/ingester.go b/server/otlp/ingester.go index 2fcf8ea048..8677cd9985 100644 --- a/server/otlp/ingester.go +++ b/server/otlp/ingester.go @@ -3,8 +3,10 @@ package otlp import ( "context" "database/sql" + "encoding/hex" "errors" "fmt" + "log" "github.com/kubeshop/tracetest/server/datastore" "github.com/kubeshop/tracetest/server/executor" @@ -13,6 +15,7 @@ import ( "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" pb "go.opentelemetry.io/proto/otlp/collector/trace/v1" + v1 "go.opentelemetry.io/proto/otlp/trace/v1" ) type runGetter interface { @@ -20,10 +23,11 @@ type runGetter interface { } type tracePersister interface { - SaveTrace(context.Context, traces.Trace) error + UpdateTraceSpans(context.Context, *traces.Trace) error } type ingester struct { + log func(string, ...interface{}) tracePersister tracePersister runGetter runGetter eventEmitter executor.EventEmitter @@ -32,6 +36,9 @@ type ingester struct { func NewIngester(tracePersister tracePersister, runRepository runGetter, eventEmitter executor.EventEmitter, dsRepo *datastore.Repository) ingester { return ingester{ + log: func(format string, args ...interface{}) { + log.Printf("[OTLP] "+format, args...) + }, tracePersister: tracePersister, runGetter: runRepository, eventEmitter: eventEmitter, @@ -43,32 +50,66 @@ func (i ingester) Ingest(ctx context.Context, request *pb.ExportTraceServiceRequ ds, err := i.dsRepo.Current(ctx) if err != nil || !ds.IsOTLPBasedProvider() { - fmt.Println("OTLP server is not enabled. Ignoring request") + i.log("OTLP server is not enabled. Ignoring request") return &pb.ExportTraceServiceResponse{}, nil } if len(request.ResourceSpans) == 0 { + i.log("no spans to ingest") return &pb.ExportTraceServiceResponse{}, nil } - modelTrace := traces.FromOtelResourceSpans(request.ResourceSpans) - err = i.tracePersister.SaveTrace(ctx, modelTrace) - if err != nil { - return nil, fmt.Errorf("failed to save trace: %w", err) - } + receivedTraces := i.traces(request.ResourceSpans) + i.log("received %d traces", len(receivedTraces)) - err = i.notify(ctx, modelTrace, requestType) - if err != nil { - return nil, fmt.Errorf("failed to notify: %w", err) + // each request can have different traces so we need to go over each individual trace + for ix, modelTrace := range receivedTraces { + i.log("processing trace %d/%d traceID %s", ix+1, len(receivedTraces), modelTrace.ID.String()) + err = i.tracePersister.UpdateTraceSpans(ctx, &modelTrace) + if err != nil { + return nil, fmt.Errorf("failed to save trace: %w", err) + } + + err = i.notify(ctx, modelTrace, requestType) + if err != nil { + return nil, fmt.Errorf("failed to notify: %w", err) + } } return &pb.ExportTraceServiceResponse{}, nil } +func (i ingester) traces(input []*v1.ResourceSpans) []traces.Trace { + spansByTrace := map[string][]*v1.Span{} + + for _, rs := range input { + for _, il := range rs.ScopeSpans { + for _, span := range il.Spans { + traceID := trace.TraceID(span.TraceId).String() + i.log("adding span %s to trace %s", hex.EncodeToString(span.SpanId), traceID) + spansByTrace[traceID] = append(spansByTrace[traceID], span) + } + } + } + + i.log("sorted %d traces", len(spansByTrace)) + + modelTraces := make([]traces.Trace, 0, len(spansByTrace)) + for traceID, spans := range spansByTrace { + i.log("creating trace %s with %d spans", traceID, len(spans)) + modelTraces = append( + modelTraces, + traces.FromSpanList(spans), + ) + } + + return modelTraces +} + func (i ingester) notify(ctx context.Context, trace traces.Trace, requestType string) error { run, err := i.runGetter.GetRunByTraceID(ctx, trace.ID) if errors.Is(err, sql.ErrNoRows) { - // trace is not part of any known test run, so no need to notify + // trace is not part of any known test run, no need to notify return nil } if err != nil { diff --git a/server/tracedb/otlp.go b/server/tracedb/otlp.go index d908977d3b..3e34fa507d 100644 --- a/server/tracedb/otlp.go +++ b/server/tracedb/otlp.go @@ -2,7 +2,10 @@ package tracedb import ( "context" + "database/sql" + "errors" + "github.com/kubeshop/tracetest/server/tracedb/connection" "github.com/kubeshop/tracetest/server/traces" "go.opentelemetry.io/otel/trace" ) @@ -41,6 +44,11 @@ func (tdb *OTLPTraceDB) GetEndpoints() string { // GetTraceByID implements TraceDB func (tdb *OTLPTraceDB) GetTraceByID(ctx context.Context, id string) (traces.Trace, error) { - return tdb.traceGetter.Get(ctx, traces.DecodeTraceID(id)) + t, err := tdb.traceGetter.Get(ctx, traces.DecodeTraceID(id)) + if errors.Is(err, sql.ErrNoRows) { + err = connection.ErrTraceNotFound + } + + return t, err } diff --git a/server/tracedb/tracedb.go b/server/tracedb/tracedb.go index 21e51c0b39..041f28dfc6 100644 --- a/server/tracedb/tracedb.go +++ b/server/tracedb/tracedb.go @@ -47,12 +47,14 @@ func (db *noopTraceDB) TestConnection(ctx context.Context) model.ConnectionResul } type traceDBFactory struct { - runRepository traceGetter + traceGetter traceGetter } -func Factory(runRepository traceGetter) func(ds datastore.DataStore) (TraceDB, error) { +type FactoryFunc func(ds datastore.DataStore) (TraceDB, error) + +func Factory(tg traceGetter) FactoryFunc { f := traceDBFactory{ - runRepository: runRepository, + traceGetter: tg, } return f.New @@ -63,7 +65,7 @@ func (f *traceDBFactory) getTraceDBInstance(ds datastore.DataStore) (TraceDB, er var err error if ds.IsOTLPBasedProvider() { - tdb, err = newCollectorDB(f.runRepository) + tdb, err = newCollectorDB(f.traceGetter) return tdb, err } diff --git a/server/traces/otel_converter.go b/server/traces/otel_converter.go index 12b81d32bc..65f62b1a35 100644 --- a/server/traces/otel_converter.go +++ b/server/traces/otel_converter.go @@ -23,11 +23,15 @@ func FromOtelResourceSpans(resourceSpans []*v1.ResourceSpans) Trace { } } + return FromSpanList(flattenSpans) +} + +func FromSpanList(input []*v1.Span) Trace { traceID := "" spans := make([]Span, 0) - for _, span := range flattenSpans { + for _, span := range input { newSpan := ConvertOtelSpanIntoSpan(span) - traceID = hex.EncodeToString(span.TraceId) + traceID = CreateTraceID(span.TraceId).String() spans = append(spans, *newSpan) } diff --git a/server/traces/spans.go b/server/traces/span_entitiess.go similarity index 100% rename from server/traces/spans.go rename to server/traces/span_entitiess.go diff --git a/server/traces/traces.go b/server/traces/trace_entities.go similarity index 100% rename from server/traces/traces.go rename to server/traces/trace_entities.go diff --git a/server/traces/trace_repository.go b/server/traces/trace_repository.go new file mode 100644 index 0000000000..a6146fd111 --- /dev/null +++ b/server/traces/trace_repository.go @@ -0,0 +1,110 @@ +package traces + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "log" + + "github.com/kubeshop/tracetest/server/pkg/sqlutil" + "go.opentelemetry.io/otel/trace" +) + +type TraceRepository struct { + db *sql.DB + log func(string, ...interface{}) +} + +func NewTraceRepository(db *sql.DB) *TraceRepository { + return &TraceRepository{ + log: func(format string, args ...interface{}) { + log.Printf("[TraceRepository] "+format, args...) + }, + db: db, + } +} + +func (r *TraceRepository) Get(ctx context.Context, id trace.TraceID) (Trace, error) { + sql, params := sqlutil.Tenant( + ctx, + `SELECT trace FROM otlp_traces WHERE trace_id = $1`, + id.String(), + ) + + var jsonTrace []byte + err := r.db.QueryRowContext(ctx, sql, params...).Scan(&jsonTrace) + if err != nil { + return Trace{}, err + } + + var t Trace + err = json.Unmarshal(jsonTrace, &t) + if err != nil { + return Trace{}, fmt.Errorf("failed to unmarshal trace: %w", err) + } + + return t, nil +} + +func (r *TraceRepository) UpdateTraceSpans(ctx context.Context, trace *Trace) error { + r.log("updating trace %s with %d spans", trace.ID.String(), len(trace.Spans())) + old, err := r.Get(ctx, trace.ID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("failed to get trace: %w", err) + } + r.log("got old trace %s with %d spans", old.ID.String(), len(old.Spans())) + + // we need to merge preexisting spans with the new spans + updatedTrace := NewTrace( + trace.ID.String(), + append(old.Spans(), trace.Spans()...), + ) + r.log("updated trace %s with %d spans", updatedTrace.ID.String(), len(updatedTrace.Spans())) + + jsonTrace, err := updatedTrace.MarshalJSON() + if err != nil { + return fmt.Errorf("failed to marshal trace: %w", err) + } + + tx, err := r.db.BeginTx(ctx, nil) + defer tx.Rollback() + if err != nil { + return fmt.Errorf("failed to begin transaction: %w", err) + } + + sql, params := sqlutil.Tenant( + ctx, + `DELETE FROM otlp_traces WHERE trace_id = $1`, + trace.ID.String(), + ) + + _, err = tx.ExecContext( + ctx, + sql, + params..., + ) + if err != nil { + return fmt.Errorf("failed to delete trace: %w", err) + } + + _, err = tx.ExecContext( + ctx, + `INSERT INTO otlp_traces (tenant_id, trace_id, trace) VALUES ($1, $2, $3)`, + sqlutil.TenantID(ctx), + trace.ID.String(), + jsonTrace, + ) + + if err != nil { + return fmt.Errorf("failed to insert trace: %w", err) + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + + return nil +} From 81244bdb8de335507ff08d90e226b3fab6fe00a4 Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 20:23:17 -0300 Subject: [PATCH 4/9] add comment --- server/executor/default_poller_executor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/executor/default_poller_executor.go b/server/executor/default_poller_executor.go index f7172b1dfa..59c660a328 100644 --- a/server/executor/default_poller_executor.go +++ b/server/executor/default_poller_executor.go @@ -120,6 +120,8 @@ func (pe DefaultPollerExecutor) ExecuteRequest(ctx context.Context, job *Job) (P done, reason := pe.donePollingTraces(job, traceDB, trace) // we need both values to be different to check for done, but after we want to have an updated job job.Run.Trace = &trace + // we need to update at this point to persist the updated trace + // otherwise we end up thingking every iteration is the first err = pe.updater.Update(ctx, job.Run) if err != nil { log.Printf("[PollerExecutor] Test %s Run %d: Update error: %s", job.Test.ID, job.Run.ID, err.Error()) From 20763d54e0d858497b11f5fc9783685fd15b3c92 Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 20:31:21 -0300 Subject: [PATCH 5/9] fix return value --- server/otlp/ingester.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/otlp/ingester.go b/server/otlp/ingester.go index 8677cd9985..360308703a 100644 --- a/server/otlp/ingester.go +++ b/server/otlp/ingester.go @@ -76,7 +76,11 @@ func (i ingester) Ingest(ctx context.Context, request *pb.ExportTraceServiceRequ } } - return &pb.ExportTraceServiceResponse{}, nil + return &pb.ExportTraceServiceResponse{ + PartialSuccess: &pb.ExportTracePartialSuccess{ + RejectedSpans: 0, + }, + }, nil } func (i ingester) traces(input []*v1.ResourceSpans) []traces.Trace { From f1a0ef1a2b00991c459baa65a767b08f45579360 Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 20:35:28 -0300 Subject: [PATCH 6/9] fix notifucation edge case --- server/otlp/ingester.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/otlp/ingester.go b/server/otlp/ingester.go index 360308703a..6fa454a11b 100644 --- a/server/otlp/ingester.go +++ b/server/otlp/ingester.go @@ -121,6 +121,11 @@ func (i ingester) notify(ctx context.Context, trace traces.Trace, requestType st return fmt.Errorf("error getting run by traceID: %w", err) } + if run.State != test.RunStateAwaitingTrace { + // run is not awaiting trace, no need to notify + return nil + } + evt := events.TraceOtlpServerReceivedSpans( run.TestID, run.ID, From 002b62335a5e66a3c786f94872e95d75bd98bbd2 Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Thu, 3 Aug 2023 21:10:03 -0300 Subject: [PATCH 7/9] fix no tracing --- cli/runner/orchestrator.go | 2 +- examples/tracetest-no-tracing/docker-compose.yml | 1 - examples/tracetest-no-tracing/tests/list-tests.yaml | 4 ---- server/executor/default_poller_executor.go | 2 ++ 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cli/runner/orchestrator.go b/cli/runner/orchestrator.go index b3c0661a6a..97fd4a3248 100644 --- a/cli/runner/orchestrator.go +++ b/cli/runner/orchestrator.go @@ -386,7 +386,7 @@ func HandleRunError(resp *http.Response, reqErr error) error { } if reqErr != nil { - return fmt.Errorf("could not run transaction: %w", err) + return fmt.Errorf("could not run transaction: %w", reqErr) } return nil diff --git a/examples/tracetest-no-tracing/docker-compose.yml b/examples/tracetest-no-tracing/docker-compose.yml index 196687a3a5..aef1b46304 100644 --- a/examples/tracetest-no-tracing/docker-compose.yml +++ b/examples/tracetest-no-tracing/docker-compose.yml @@ -3,7 +3,6 @@ services: tracetest: image: kubeshop/tracetest:${TAG:-latest} - platform: linux/amd64 volumes: - type: bind source: ./tracetest-config.yaml diff --git a/examples/tracetest-no-tracing/tests/list-tests.yaml b/examples/tracetest-no-tracing/tests/list-tests.yaml index 56f2856ff6..926d6e4b8f 100644 --- a/examples/tracetest-no-tracing/tests/list-tests.yaml +++ b/examples/tracetest-no-tracing/tests/list-tests.yaml @@ -11,10 +11,6 @@ spec: headers: - key: Content-Type value: application/json - grpc: - protobufFile: "" - address: "" - method: "" specs: - selector: span[name = "Tracetest trigger"] assertions: diff --git a/server/executor/default_poller_executor.go b/server/executor/default_poller_executor.go index 59c660a328..809e7974d0 100644 --- a/server/executor/default_poller_executor.go +++ b/server/executor/default_poller_executor.go @@ -117,9 +117,11 @@ func (pe DefaultPollerExecutor) ExecuteRequest(ctx context.Context, job *Job) (P return PollResult{}, err } + trace.ID = job.Run.TraceID done, reason := pe.donePollingTraces(job, traceDB, trace) // we need both values to be different to check for done, but after we want to have an updated job job.Run.Trace = &trace + // we need to update at this point to persist the updated trace // otherwise we end up thingking every iteration is the first err = pe.updater.Update(ctx, job.Run) From d046ae4b2e6fbde5143dcfd84b52b0d20b97bfcc Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Fri, 4 Aug 2023 12:01:47 -0300 Subject: [PATCH 8/9] Update server/executor/default_poller_executor.go Co-authored-by: Daniel Baptista Dias --- server/executor/default_poller_executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/executor/default_poller_executor.go b/server/executor/default_poller_executor.go index 809e7974d0..50f858bf26 100644 --- a/server/executor/default_poller_executor.go +++ b/server/executor/default_poller_executor.go @@ -123,7 +123,7 @@ func (pe DefaultPollerExecutor) ExecuteRequest(ctx context.Context, job *Job) (P job.Run.Trace = &trace // we need to update at this point to persist the updated trace - // otherwise we end up thingking every iteration is the first + // otherwise we end up thinking every iteration is the first err = pe.updater.Update(ctx, job.Run) if err != nil { log.Printf("[PollerExecutor] Test %s Run %d: Update error: %s", job.Test.ID, job.Run.ID, err.Error()) From 30e3acebaf6c6e93eac79d9b09f4b3d0ae7ab06a Mon Sep 17 00:00:00 2001 From: Sebastian Choren Date: Fri, 4 Aug 2023 12:06:00 -0300 Subject: [PATCH 9/9] fix merge error --- server/traces/otel_converter.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/traces/otel_converter.go b/server/traces/otel_converter.go index 65f62b1a35..a99d320da8 100644 --- a/server/traces/otel_converter.go +++ b/server/traces/otel_converter.go @@ -54,9 +54,9 @@ func ConvertOtelSpanIntoSpan(span *v1.Span) *Span { endTime = time.Unix(0, int64(span.GetEndTimeUnixNano())) } - var spanStatus *model.SpanStatus + var spanStatus *SpanStatus if span.Status != nil { - spanStatus = &model.SpanStatus{ + spanStatus = &SpanStatus{ Code: span.Status.Code.String(), Description: span.Status.Message, }