From de26d24bac9511cce42d7618eeac77aa35813c02 Mon Sep 17 00:00:00 2001 From: Daniel Baptista Dias Date: Thu, 30 Mar 2023 16:42:34 -0300 Subject: [PATCH] Add TestSpec events (#2280) * Adding logic to find events * Added tests to event emitter on Test Spec validations * Updating constants * Fixing mappings * Adding PR suggestions * Revert HTTP mapping change --- server/executor/assertion_runner.go | 10 + server/model/events/events.go | 399 ++++++++++++++++++++++++++++ server/model/test_run_event.go | 1 + server/model/test_run_event_test.go | 19 ++ 4 files changed, 429 insertions(+) create mode 100644 server/model/events/events.go create mode 100644 server/model/test_run_event_test.go diff --git a/server/executor/assertion_runner.go b/server/executor/assertion_runner.go index f2b45f89bf..57a767ac72 100644 --- a/server/executor/assertion_runner.go +++ b/server/executor/assertion_runner.go @@ -8,6 +8,7 @@ import ( "github.com/kubeshop/tracetest/server/expression" "github.com/kubeshop/tracetest/server/model" + "github.com/kubeshop/tracetest/server/model/events" "github.com/kubeshop/tracetest/server/subscription" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/propagation" @@ -36,6 +37,7 @@ type defaultAssertionRunner struct { inputChannel chan AssertionRequest exitChannel chan bool subscriptionManager *subscription.Manager + eventEmitter EventEmitter } var _ WorkerPool = &defaultAssertionRunner{} @@ -54,6 +56,7 @@ func NewAssertionRunner( assertionExecutor: assertionExecutor, inputChannel: make(chan AssertionRequest, 1), subscriptionManager: subscriptionManager, + eventEmitter: eventEmitter, } } @@ -103,9 +106,13 @@ func (e *defaultAssertionRunner) startWorker() { func (e *defaultAssertionRunner) runAssertionsAndUpdateResult(ctx context.Context, request AssertionRequest) (model.Run, error) { log.Printf("[AssertionRunner] Test %s Run %d: Starting\n", request.Test.ID, request.Run.ID) + + e.eventEmitter.Emit(ctx, events.TestSpecsRunStart(request.Test.ID, request.Run.ID)) + run, err := e.executeAssertions(ctx, request) if err != nil { log.Printf("[AssertionRunner] Test %s Run %d: error executing assertions: %s\n", request.Test.ID, request.Run.ID, err.Error()) + e.eventEmitter.Emit(ctx, events.TestSpecsRunError(request.Test.ID, request.Run.ID, err)) return model.Run{}, e.updater.Update(ctx, run.Failed(err)) } log.Printf("[AssertionRunner] Test %s Run %d: Success. pass: %d, fail: %d\n", request.Test.ID, request.Run.ID, run.Pass, run.Fail) @@ -113,9 +120,12 @@ func (e *defaultAssertionRunner) runAssertionsAndUpdateResult(ctx context.Contex err = e.updater.Update(ctx, run) if err != nil { log.Printf("[AssertionRunner] Test %s Run %d: error updating run: %s\n", request.Test.ID, request.Run.ID, err.Error()) + e.eventEmitter.Emit(ctx, events.TestSpecsRunError(request.Test.ID, request.Run.ID, err)) return model.Run{}, fmt.Errorf("could not save result on database: %w", err) } + e.eventEmitter.Emit(ctx, events.TestSpecsRunSuccess(request.Test.ID, request.Run.ID)) + return run, nil } diff --git a/server/model/events/events.go b/server/model/events/events.go new file mode 100644 index 0000000000..1f10ead1aa --- /dev/null +++ b/server/model/events/events.go @@ -0,0 +1,399 @@ +package events + +import ( + "fmt" + "time" + + "github.com/kubeshop/tracetest/server/id" + "github.com/kubeshop/tracetest/server/model" +) + +func TriggerCreatedInfo(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "CREATED_INFO", + Title: "Trigger Run has been created", + Description: "Trigger Run has been created", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerResolveError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "RESOLVE_ERROR", + Title: "Resolving trigger details failed", + Description: "Resolving trigger details failed", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerResolveSuccess(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "RESOLVE_SUCCESS", + Title: "Successful resolving of trigger details", + Description: "Successful resolving of trigger details", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerResolveStart(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "RESOLVE_START", + Title: "Resolving trigger details based on environment variables", + Description: "Resolving trigger details based on environment variables", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerExecutionStart(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "EXECUTION_START", + Title: "Initial trigger execution", + Description: "Initial trigger execution", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerExecutionSuccess(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "EXECUTION_SUCCESS", + Title: "Successful trigger execution", + Description: "Successful trigger execution", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerHTTPUnreachableHostError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "HTTP_UNREACHABLE_HOST_ERROR", + Title: "Tracetest could not reach the defined host in the trigger", + Description: "Tracetest could not reach the defined host in the trigger", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggerDockerComposeHostMismatchError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "DOCKER_COMPOSE_HOST_MISMATCH_ERROR", + Title: "Tracetest is running inside a Docker container", + Description: "We identified Tracetest is running inside a docker compose container, so if you are trying to access your local host machine please use the host.docker.internal hostname. For more information, see https://docs.docker.com/docker-for-mac/networking/#use-cases-and-workarounds", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TriggergRPCUnreachableHostError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrigger, + Type: "GRPC_UNREACHABLE_HOST_ERROR", + Title: "Tracetest could not reach the defined host in the trigger", + Description: "Tracetest could not reach the defined host in the trigger", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TraceFetchingStart(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "FETCHING_START", + Title: "Starting the trace fetching process", + Description: "Starting the trace fetching process", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TraceQueuedInfo(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "QUEUED_INFO", + Title: "Trace Run has been queued to start the fetching process", + Description: "Trace Run has been queued to start the fetching process", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TraceDataStoreConnectionInfo(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "DATA_STORE_CONNECTION_INFO", + Title: "A Data store connection request has been executed,test connection result information", + Description: "A Data store connection request has been executed,test connection result information", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TracePollingStart(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "POLLING_START", + Title: "Starting the trace polling process", + Description: "Starting the trace polling process", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TracePollingIterationInfo(testID id.ID, runID int, numberOfSpans, iteration int, nextIterationReason string) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "POLLING_ITERATION_INFO", + Title: "A polling iteration has been executed", + Description: fmt.Sprintf("A polling iteration has been executed, %d spans - iteration %d - reason of next iteration: %s", numberOfSpans, iteration, nextIterationReason), + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TracePollingSuccess(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "POLLING_SUCCESS", + Title: "The polling strategy has succeeded in fetching the trace from the Data Store", + Description: "The polling strategy has succeeded in fetching the trace from the Data Store", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TracePollingError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "POLLING_ERROR", + Title: "The polling strategy has failed to fetch the trace", + Description: "The polling strategy has failed to fetch the trace", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TraceFetchingSuccess(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "FETCHING_SUCCESS", + Title: "The trace was successfully processed by the backend", + Description: "The trace was successfully processed by the backend", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TraceFetchingError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "FETCHING_ERROR", + Title: "The trace was not able to be fetched", + Description: "The trace was not able to be fetched", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TraceStoppedInfo(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTrace, + Type: "STOPPED_INFO", + Title: "The test run was stopped during its execution", + Description: "The test run was stopped during its execution", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestOutputGenerationWarning(testID id.ID, runID int, output string) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "OUTPUT_GENERATION_WARNING", + Title: fmt.Sprintf("Output %s not be generated", output), + Description: fmt.Sprintf("The value for output %s could not be generated", output), + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestResolveStart(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "RESOLVE_START", + Title: "Resolving test specs details start", + Description: "Resolving test specs details start", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestResolveSuccess(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "RESOLVE_SUCCESS", + Title: "Resolving test specs details success", + Description: "Resolving test specs details success", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestResolveError(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "RESOLVE_ERROR", + Title: "An error ocurred while parsing the test specs", + Description: "An error ocurred while parsing the test specs", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestSpecsRunSuccess(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "TEST_SPECS_RUN_SUCCESS", + Title: "Test Specs were successfully executed", + Description: "Test Specs were successfully executed", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestSpecsRunError(testID id.ID, runID int, err error) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "TEST_SPECS_RUN_ERROR", + Title: "Test specs execution error", + Description: fmt.Sprintf("An error happened when trying to run test specs. Error: %s", err.Error()), + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} + +func TestSpecsRunStart(testID id.ID, runID int) model.TestRunEvent { + return model.TestRunEvent{ + TestID: testID, + RunID: runID, + Stage: model.StageTest, + Type: "TEST_SPECS_RUN_START", + Title: "Test specs execution start", + Description: "Test specs execution start", + CreatedAt: time.Now(), + DataStoreConnection: model.ConnectionResult{}, + Polling: model.PollingInfo{}, + Outputs: []model.OutputInfo{}, + } +} diff --git a/server/model/test_run_event.go b/server/model/test_run_event.go index 4665e1a02d..a603a02206 100644 --- a/server/model/test_run_event.go +++ b/server/model/test_run_event.go @@ -11,6 +11,7 @@ type ( Protocol string Status string TestRunEventStage string + TestRunEventType string PollingType string LogLevel string ) diff --git a/server/model/test_run_event_test.go b/server/model/test_run_event_test.go new file mode 100644 index 0000000000..f36bd613dd --- /dev/null +++ b/server/model/test_run_event_test.go @@ -0,0 +1,19 @@ +package model_test + +import ( + "fmt" + "testing" + + "github.com/kubeshop/tracetest/server/id" + "github.com/kubeshop/tracetest/server/model" + "github.com/stretchr/testify/assert" +) + +func TestTestRunEvent_ResourceID(t *testing.T) { + testID := id.NewRandGenerator().ID() + runID := 1 + + event := model.TestRunEvent{TestID: testID, RunID: runID} + + assert.Equal(t, event.ResourceID(), fmt.Sprintf("test/%s/run/%d/event", testID, runID)) +}