From 8f3f8de62fdf06d59015e35c6f333f5fa66a504e Mon Sep 17 00:00:00 2001 From: Vladislav Sukhin Date: Tue, 10 Dec 2024 00:09:32 +0300 Subject: [PATCH] feat: [TKC-2882] stream service and parallel step logs (#6052) * feat: api methods for service logs Signed-off-by: Vladislav Sukhin * fix: client for get service logs Signed-off-by: Vladislav Sukhin * fix: change log Signed-off-by: Vladislav Sukhin * fix: disable hints Signed-off-by: Vladislav Sukhin * fix: routing Signed-off-by: Vladislav Sukhin * fix: show service logs Signed-off-by: Vladislav Sukhin * fix: check service name Signed-off-by: Vladislav Sukhin * fix: check service name Signed-off-by: Vladislav Sukhin * fix: log comment Signed-off-by: Vladislav Sukhin * fix: check for testworkflow service Signed-off-by: Vladislav Sukhin * fix: friendly error Signed-off-by: Vladislav Sukhin * fix: add spinner Signed-off-by: Vladislav Sukhin * feat: proto for service notifications Signed-off-by: Vladislav Sukhin * feat: add cloud grpc method for server notifications Signed-off-by: Vladislav Sukhin * fix: change timeeout Signed-off-by: Vladislav Sukhin * fix: waiting for service pod Signed-off-by: Vladislav Sukhin * fix: typo Signed-off-by: Vladislav Sukhin * fix: waiting for service pod Signed-off-by: Vladislav Sukhin * fix: add service name check Signed-off-by: Vladislav Sukhin * fix: adjust help Signed-off-by: Vladislav Sukhin * fix: add method to parallel step Signed-off-by: Vladislav Sukhin * fix: use retry library Signed-off-by: Vladislav Sukhin * fix: rename const Signed-off-by: Vladislav Sukhin * fix: 0 attempts Signed-off-by: Vladislav Sukhin * fix: use option Signed-off-by: Vladislav Sukhin * fix: remove ctx Signed-off-by: Vladislav Sukhin * feat: add cli support for parallel steps Signed-off-by: Vladislav Sukhin * fix: rename url Signed-off-by: Vladislav Sukhin * feat: api methods for parallel steps Signed-off-by: Vladislav Sukhin * fix: tune parallel step detection Signed-off-by: Vladislav Sukhin * fix: typo Signed-off-by: Vladislav Sukhin * fix: cli Signed-off-by: Vladislav Sukhin * feat: add proto for parallel step logs Signed-off-by: Vladislav Sukhin * fix: lint Signed-off-by: Vladislav Sukhin * add: grpc method for parallel steps Signed-off-by: Vladislav Sukhin * fix: comments Signed-off-by: Vladislav Sukhin * fix: comments Signed-off-by: Vladislav Sukhin * fix: typo Signed-off-by: Vladislav Sukhin * fix: change proto Signed-off-by: Vladislav Sukhin * fix: rename fields Signed-off-by: Vladislav Sukhin * fix: use const Signed-off-by: Vladislav Sukhin * fix: check for empty result Signed-off-by: Vladislav Sukhin * fix: move methods to agent package Signed-off-by: Vladislav Sukhin * fix: send org and env ids Signed-off-by: Vladislav Sukhin * fix: add org and env ids Signed-off-by: Vladislav Sukhin --------- Signed-off-by: Vladislav Sukhin --- cmd/api-server/commons/commons.go | 26 +- cmd/api-server/main.go | 29 +- .../commands/testworkflows/executions.go | 2 +- .../commands/testworkflows/run.go | 156 +++- .../commands/testworkflows/watch.go | 22 +- internal/app/api/v1/server.go | 4 + internal/app/api/v1/testworkflowexecutions.go | 226 +++++- internal/config/config.go | 86 +-- internal/config/procontext.go | 26 +- pkg/agent/agent.go | 48 +- pkg/agent/agent_test.go | 17 +- pkg/agent/events_test.go | 17 +- pkg/agent/logs_test.go | 15 +- pkg/agent/testworkflows.go | 478 +++++++++++- pkg/api/v1/client/interface.go | 2 + pkg/api/v1/client/testworkflow.go | 16 + .../model_test_workflow_execution_extended.go | 15 + .../testkube/model_test_workflow_extended.go | 19 + .../model_test_workflow_signature_extended.go | 24 + .../model_test_workflow_step_extended.go | 19 + ...el_test_workflow_step_parallel_extended.go | 15 + pkg/cloud/service.pb.go | 701 +++++++++++++++--- pkg/cloud/service_grpc.pb.go | 136 ++++ proto/service.proto | 36 + 24 files changed, 1880 insertions(+), 255 deletions(-) diff --git a/cmd/api-server/commons/commons.go b/cmd/api-server/commons/commons.go index 2754634026b..e10813b5c64 100644 --- a/cmd/api-server/commons/commons.go +++ b/cmd/api-server/commons/commons.go @@ -282,18 +282,20 @@ func ReadDefaultExecutors(cfg *config.Config) (executors []testkube.ExecutorDeta func ReadProContext(ctx context.Context, cfg *config.Config, grpcClient cloud.TestKubeCloudAPIClient) config.ProContext { proContext := config.ProContext{ - APIKey: cfg.TestkubeProAPIKey, - URL: cfg.TestkubeProURL, - TLSInsecure: cfg.TestkubeProTLSInsecure, - WorkerCount: cfg.TestkubeProWorkerCount, - LogStreamWorkerCount: cfg.TestkubeProLogStreamWorkerCount, - WorkflowNotificationsWorkerCount: cfg.TestkubeProWorkflowNotificationsWorkerCount, - SkipVerify: cfg.TestkubeProSkipVerify, - EnvID: cfg.TestkubeProEnvID, - OrgID: cfg.TestkubeProOrgID, - Migrate: cfg.TestkubeProMigrate, - ConnectionTimeout: cfg.TestkubeProConnectionTimeout, - DashboardURI: cfg.TestkubeDashboardURI, + APIKey: cfg.TestkubeProAPIKey, + URL: cfg.TestkubeProURL, + TLSInsecure: cfg.TestkubeProTLSInsecure, + WorkerCount: cfg.TestkubeProWorkerCount, + LogStreamWorkerCount: cfg.TestkubeProLogStreamWorkerCount, + WorkflowNotificationsWorkerCount: cfg.TestkubeProWorkflowNotificationsWorkerCount, + WorkflowServiceNotificationsWorkerCount: cfg.TestkubeProWorkflowServiceNotificationsWorkerCount, + WorkflowParallelStepNotificationsWorkerCount: cfg.TestkubeProWorkflowParallelStepNotificationsWorkerCount, + SkipVerify: cfg.TestkubeProSkipVerify, + EnvID: cfg.TestkubeProEnvID, + OrgID: cfg.TestkubeProOrgID, + Migrate: cfg.TestkubeProMigrate, + ConnectionTimeout: cfg.TestkubeProConnectionTimeout, + DashboardURI: cfg.TestkubeDashboardURI, } if cfg.TestkubeProAPIKey == "" || grpcClient == nil { diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index f3bcd976f29..de8c8bd41b4 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "flag" "fmt" "strings" @@ -22,12 +21,10 @@ import ( "github.com/kubeshop/testkube/pkg/event/kind/k8sevent" "github.com/kubeshop/testkube/pkg/event/kind/webhook" ws "github.com/kubeshop/testkube/pkg/event/kind/websocket" - "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/secretmanager" "github.com/kubeshop/testkube/pkg/server" "github.com/kubeshop/testkube/pkg/tcl/checktcl" "github.com/kubeshop/testkube/pkg/tcl/schedulertcl" - "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/presets" "github.com/kubeshop/testkube/internal/common" @@ -308,26 +305,8 @@ func main() { api.Init(httpServer) log.DefaultLogger.Info("starting agent service") - getTestWorkflowNotificationsStream := func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) { - execution, err := testWorkflowResultsRepository.Get(ctx, executionID) - if err != nil { - return nil, err - } - notifications := executionWorker.Notifications(ctx, execution.Id, executionworkertypes.NotificationsOptions{ - Hints: executionworkertypes.Hints{ - Namespace: execution.Namespace, - Signature: execution.Signature, - ScheduledAt: common.Ptr(execution.ScheduledAt), - }, - }) - if notifications.Err() != nil { - return nil, notifications.Err() - } - return notifications.Channel(), nil - } - getDeprecatedLogStream := func(ctx context.Context, executionID string) (chan output.Output, error) { - return nil, errors.New("deprecated features have been disabled") - } + + getDeprecatedLogStream := agent.GetDeprecatedLogStream if deprecatedSystem != nil && deprecatedSystem.StreamLogs != nil { getDeprecatedLogStream = deprecatedSystem.StreamLogs } @@ -336,7 +315,9 @@ func main() { httpServer.Mux.Handler(), grpcClient, getDeprecatedLogStream, - getTestWorkflowNotificationsStream, + agent.GetTestWorkflowNotificationsStream(testWorkflowResultsRepository, executionWorker), + agent.GetTestWorkflowServiceNotificationsStream(testWorkflowResultsRepository, executionWorker), + agent.GetTestWorkflowParallelStepNotificationsStream(testWorkflowResultsRepository, executionWorker), clusterId, cfg.TestkubeClusterName, features, diff --git a/cmd/kubectl-testkube/commands/testworkflows/executions.go b/cmd/kubectl-testkube/commands/testworkflows/executions.go index 66e297df848..bdd328da32c 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/executions.go +++ b/cmd/kubectl-testkube/commands/testworkflows/executions.go @@ -75,7 +75,7 @@ func NewGetTestWorkflowExecutionsCmd() *cobra.Command { ui.Info("Getting logs for test workflow execution", executionID) logs, err := client.GetTestWorkflowExecutionLogs(executionID) - ui.ExitOnError("getting logs from executor", err) + ui.ExitOnError("getting logs from test workflow", err) sigs := flattenSignatures(execution.Signature) diff --git a/cmd/kubectl-testkube/commands/testworkflows/run.go b/cmd/kubectl-testkube/commands/testworkflows/run.go index 5c96c690d39..65725fea50a 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/run.go +++ b/cmd/kubectl-testkube/commands/testworkflows/run.go @@ -29,6 +29,7 @@ import ( const ( LogTimestampLength = 30 // time.RFC3339Nano without 00:00 timezone apiErrorMessage = "processing error:" + logsCheckDelay = 100 * time.Millisecond ) var ( @@ -47,6 +48,10 @@ func NewRunTestWorkflowCmd() *cobra.Command { masks []string tags map[string]string selectors []string + serviceName string + parallelStepName string + serviceIndex int + parallelStepIndex int ) cmd := &cobra.Command{ @@ -146,7 +151,15 @@ func NewRunTestWorkflowCmd() *cobra.Command { ui.NL() if !execution.FailedToInitialize() { if watchEnabled && len(args) > 0 { - exitCode = uiWatch(execution, client) + var pServiceName, pParallelStepName *string + if cmd.Flag("service-name").Changed || cmd.Flag("service-index").Changed { + pServiceName = &serviceName + } + if cmd.Flag("parallel-step-name").Changed || cmd.Flag("parallel-step-index").Changed { + pParallelStepName = ¶llelStepName + } + + exitCode = uiWatch(execution, pServiceName, serviceIndex, pParallelStepName, parallelStepIndex, client) ui.NL() if downloadArtifactsEnabled { tests.DownloadTestWorkflowArtifacts(execution.Id, downloadDir, format, masks, client, outputPretty) @@ -181,12 +194,46 @@ func NewRunTestWorkflowCmd() *cobra.Command { cmd.Flags().StringArrayVarP(&masks, "mask", "", []string{}, "regexp to filter downloaded files, single or comma separated, like report/.* or .*\\.json,.*\\.js$") cmd.Flags().StringToStringVarP(&tags, "tag", "", map[string]string{}, "execution tags in a form of name1=val1 passed to executor") cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1 or label expression") + cmd.Flags().StringVar(&serviceName, "service-name", "", "test workflow service name") + cmd.Flags().IntVar(&serviceIndex, "service-index", 0, "test workflow service index starting from 0") + cmd.Flags().StringVar(¶llelStepName, "parallel-step-name", "", "test workflow parallel step name or reference") + cmd.Flags().IntVar(¶llelStepIndex, "parallel-step-index", 0, "test workflow parallel step index starting from 0") return cmd } -func uiWatch(execution testkube.TestWorkflowExecution, client apiclientv1.Client) int { - result, err := watchTestWorkflowLogs(execution.Id, execution.Signature, client) +func uiWatch(execution testkube.TestWorkflowExecution, serviceName *string, serviceIndex int, + parallelStepName *string, parallelStepIndex int, client apiclientv1.Client) int { + var result *testkube.TestWorkflowResult + var err error + + switch { + case serviceName != nil: + found := false + if execution.Workflow != nil { + found = execution.Workflow.HasService(*serviceName) + } + + if !found { + ui.Failf("unknown service '%s' for test workflow execution %s", *serviceName, execution.Id) + } + + result, err = watchTestWorkflowServiceLogs(execution.Id, *serviceName, serviceIndex, execution.Signature, client) + case parallelStepName != nil: + ref := execution.GetParallelStepReference(*parallelStepName) + if ref == "" { + ui.Failf("unknown parallel step '%s' for test workflow execution %s", *parallelStepName, execution.Id) + } + + result, err = watchTestWorkflowParallelStepLogs(execution.Id, ref, parallelStepIndex, execution.Signature, client) + default: + result, err = watchTestWorkflowLogs(execution.Id, execution.Signature, client) + } + + if result == nil && err == nil { + err = errors.New("no result found") + } + ui.ExitOnError("reading test workflow execution logs", err) // Apply the result in the execution @@ -283,15 +330,10 @@ func getTimestampLength(line string) int { return 0 } -func watchTestWorkflowLogs(id string, signature []testkube.TestWorkflowSignature, client apiclientv1.Client) (*testkube.TestWorkflowResult, error) { - ui.Info("Getting logs from test workflow job", id) - - notifications, err := client.GetTestWorkflowExecutionNotifications(id) - ui.ExitOnError("getting logs from executor", err) - +func printTestWorkflowLogs(signature []testkube.TestWorkflowSignature, + notifications chan testkube.TestWorkflowExecutionNotification) (result *testkube.TestWorkflowResult) { steps := flattenSignatures(signature) - var result *testkube.TestWorkflowResult var isLineBeginning = true for l := range notifications { if l.Output != nil { @@ -309,8 +351,100 @@ func watchTestWorkflowLogs(id string, signature []testkube.TestWorkflowSignature } ui.NL() + return result +} + +func watchTestWorkflowLogs(id string, signature []testkube.TestWorkflowSignature, client apiclientv1.Client) (*testkube.TestWorkflowResult, error) { + ui.Info("Getting logs from test workflow job", id) + + notifications, err := client.GetTestWorkflowExecutionNotifications(id) + if err != nil { + return nil, err + } + + return printTestWorkflowLogs(signature, notifications), nil +} + +func watchTestWorkflowServiceLogs(id, serviceName string, serviceIndex int, + signature []testkube.TestWorkflowSignature, client apiclientv1.Client) (*testkube.TestWorkflowResult, error) { + ui.Info("Getting logs from test workflow service job", fmt.Sprintf("%s-%s-%d", id, serviceName, serviceIndex)) + + var ( + notifications chan testkube.TestWorkflowExecutionNotification + nErr error + ) + + spinner := ui.NewSpinner("Waiting for service logs") + for { + notifications, nErr = client.GetTestWorkflowExecutionServiceNotifications(id, serviceName, serviceIndex) + if nErr != nil { + execution, cErr := client.GetTestWorkflowExecution(id) + if cErr != nil { + spinner.Fail() + return nil, cErr + } + + if execution.Result != nil { + if execution.Result.IsFinished() { + nErr = errors.New("test workflow execution is finished") + } else { + time.Sleep(logsCheckDelay) + continue + } + } + } + + if nErr != nil { + spinner.Fail() + return nil, nErr + } + + break + } + + spinner.Success() + return printTestWorkflowLogs(signature, notifications), nil +} + +func watchTestWorkflowParallelStepLogs(id, ref string, workerIndex int, + signature []testkube.TestWorkflowSignature, client apiclientv1.Client) (*testkube.TestWorkflowResult, error) { + ui.Info("Getting logs from test workflow parallel step job", fmt.Sprintf("%s-%s-%d", id, ref, workerIndex)) + + var ( + notifications chan testkube.TestWorkflowExecutionNotification + nErr error + ) + + spinner := ui.NewSpinner("Waiting for parallel step logs") + for { + notifications, nErr = client.GetTestWorkflowExecutionParallelStepNotifications(id, ref, workerIndex) + if nErr != nil { + execution, cErr := client.GetTestWorkflowExecution(id) + if cErr != nil { + spinner.Fail() + return nil, cErr + } + + if execution.Result != nil { + if execution.Result.IsFinished() { + nErr = errors.New("test workflow execution is finished") + } else { + time.Sleep(logsCheckDelay) + continue + } + } + } + + if nErr != nil { + spinner.Fail() + return nil, nErr + } + + break + } - return result, err + spinner.Success() + return printTestWorkflowLogs(signature, notifications), nil } func printStatusHeader(i, n int, name string) { diff --git a/cmd/kubectl-testkube/commands/testworkflows/watch.go b/cmd/kubectl-testkube/commands/testworkflows/watch.go index 2387810b6ef..eb9c7c7769f 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/watch.go +++ b/cmd/kubectl-testkube/commands/testworkflows/watch.go @@ -13,6 +13,13 @@ import ( ) func NewWatchTestWorkflowExecutionCmd() *cobra.Command { + var ( + serviceName string + parallelStepName string + serviceIndex int + parallelStepIndex int + ) + cmd := &cobra.Command{ Use: "testworkflowexecution ", Aliases: []string{"testworkflowexecutions", "twe", "tw"}, @@ -31,7 +38,15 @@ func NewWatchTestWorkflowExecutionCmd() *cobra.Command { ui.ExitOnError("render test workflow execution", err) ui.NL() - exitCode := uiWatch(execution, client) + var pServiceName, pParallelStepName *string + if cmd.Flag("service-name").Changed || cmd.Flag("service-index").Changed { + pServiceName = &serviceName + } + if cmd.Flag("parallel-step-name").Changed || cmd.Flag("parallel-step-index").Changed { + pParallelStepName = ¶llelStepName + } + + exitCode := uiWatch(execution, pServiceName, serviceIndex, pParallelStepName, parallelStepIndex, client) ui.NL() execution, err = client.GetTestWorkflowExecution(execution.Id) @@ -43,5 +58,10 @@ func NewWatchTestWorkflowExecutionCmd() *cobra.Command { }, } + cmd.Flags().StringVar(&serviceName, "service-name", "", "test workflow service name") + cmd.Flags().IntVar(&serviceIndex, "service-index", 0, "test workflow service index starting from 0") + cmd.Flags().StringVar(¶llelStepName, "parallel-step-name", "", "test workflow parallel step name or reference") + cmd.Flags().IntVar(¶llelStepIndex, "parallel-step-index", 0, "test workflow parallel step index starting from 0") + return cmd } diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go index 615f1e52c77..93e4b0d79b3 100644 --- a/internal/app/api/v1/server.go +++ b/internal/app/api/v1/server.go @@ -150,7 +150,11 @@ func (s *TestkubeAPI) Init(server server.HTTPServer) { testWorkflowExecutions.Post("/", s.ExecuteTestWorkflowHandler()) testWorkflowExecutions.Get("/:executionID", s.GetTestWorkflowExecutionHandler()) testWorkflowExecutions.Get("/:executionID/notifications", s.StreamTestWorkflowExecutionNotificationsHandler()) + testWorkflowExecutions.Get("/:executionID/notifications/services/:serviceName/:serviceIndex", s.StreamTestWorkflowExecutionServiceNotificationsHandler()) + testWorkflowExecutions.Get("/:executionID/notifications/parallel-steps/:ref/:workerIndex", s.StreamTestWorkflowExecutionParallelStepNotificationsHandler()) testWorkflowExecutions.Get("/:executionID/notifications/stream", s.StreamTestWorkflowExecutionNotificationsWebSocketHandler()) + testWorkflowExecutions.Get("/:executionID/notifications/stream/services/:serviceName/:serviceIndex", s.StreamTestWorkflowExecutionServiceNotificationsWebSocketHandler()) + testWorkflowExecutions.Get("/:executionID/notifications/stream/parallel-steps/:ref/:workerIndex", s.StreamTestWorkflowExecutionParallelStepNotificationsWebSocketHandler()) testWorkflowExecutions.Post("/:executionID/abort", s.AbortTestWorkflowExecutionHandler()) testWorkflowExecutions.Post("/:executionID/pause", s.PauseTestWorkflowExecutionHandler()) testWorkflowExecutions.Post("/:executionID/resume", s.ResumeTestWorkflowExecutionHandler()) diff --git a/internal/app/api/v1/testworkflowexecutions.go b/internal/app/api/v1/testworkflowexecutions.go index 637ee3609f8..ebd859698dd 100644 --- a/internal/app/api/v1/testworkflowexecutions.go +++ b/internal/app/api/v1/testworkflowexecutions.go @@ -14,6 +14,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/websocket/v2" "github.com/pkg/errors" + "github.com/valyala/fasthttp" "github.com/kubeshop/testkube/internal/app/api/apiutils" "github.com/kubeshop/testkube/internal/common" @@ -23,6 +24,41 @@ import ( "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" ) +func (s *TestkubeAPI) streamNotifications(ctx *fasthttp.RequestCtx, id string, notifications executionworkertypes.NotificationsWatcher) { + // Initiate processing event stream + ctx.SetContentType("text/event-stream") + ctx.Response.Header.Set("Cache-Control", "no-cache") + ctx.Response.Header.Set("Connection", "keep-alive") + ctx.Response.Header.Set("Transfer-Encoding", "chunked") + + // Stream the notifications + ctx.SetBodyStreamWriter(func(w *bufio.Writer) { + err := w.Flush() + if err != nil { + s.Log.Errorw("could not flush stream body", "error", err, "id", id) + } + + enc := json.NewEncoder(w) + + for n := range notifications.Channel() { + err := enc.Encode(n) + if err != nil { + s.Log.Errorw("could not encode value", "error", err, "id", id) + } + + _, err = fmt.Fprintf(w, "\n") + if err != nil { + s.Log.Errorw("could not print new line", "error", err, "id", id) + } + + err = w.Flush() + if err != nil { + s.Log.Errorw("could not flush stream body", "error", err, "id", id) + } + } + }) +} + func (s *TestkubeAPI) StreamTestWorkflowExecutionNotificationsHandler() fiber.Handler { return func(c *fiber.Ctx) error { ctx := c.Context() @@ -47,39 +83,85 @@ func (s *TestkubeAPI) StreamTestWorkflowExecutionNotificationsHandler() fiber.Ha return s.BadRequest(c, errPrefix, "fetching notifications", notifications.Err()) } - // Initiate processing event stream - ctx.SetContentType("text/event-stream") - ctx.Response.Header.Set("Cache-Control", "no-cache") - ctx.Response.Header.Set("Connection", "keep-alive") - ctx.Response.Header.Set("Transfer-Encoding", "chunked") + s.streamNotifications(ctx, id, notifications) + return nil + } +} - // Stream the notifications - ctx.SetBodyStreamWriter(func(w *bufio.Writer) { - err := w.Flush() - if err != nil { - s.Log.Errorw("could not flush stream body", "error", err, "id", id) - } +func (s *TestkubeAPI) StreamTestWorkflowExecutionServiceNotificationsHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + ctx := c.Context() + executionID := c.Params("executionID") + serviceName := c.Params("serviceName") + serviceIndex := c.Params("serviceIndex") + errPrefix := fmt.Sprintf("failed to stream test workflow execution service '%s' instance '%s' notifications '%s'", + serviceName, serviceIndex, executionID) - enc := json.NewEncoder(w) + // Fetch execution from database + execution, err := s.TestWorkflowResults.Get(ctx, executionID) + if err != nil { + return s.ClientError(c, errPrefix, err) + } - for n := range notifications.Channel() { - err := enc.Encode(n) - if err != nil { - s.Log.Errorw("could not encode value", "error", err, "id", id) - } + found := false + if execution.Workflow != nil { + found = execution.Workflow.HasService(serviceName) + } - _, err = fmt.Fprintf(w, "\n") - if err != nil { - s.Log.Errorw("could not print new line", "error", err, "id", id) - } + if !found { + return s.ClientError(c, errPrefix, errors.New("unknown service for test workflow execution")) + } - err = w.Flush() - if err != nil { - s.Log.Errorw("could not flush stream body", "error", err, "id", id) - } - } + // Check for the logs + id := fmt.Sprintf("%s-%s-%s", execution.Id, serviceName, serviceIndex) + notifications := s.ExecutionWorkerClient.Notifications(ctx, id, executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, }) + if notifications.Err() != nil { + return s.BadRequest(c, errPrefix, "fetching notifications", notifications.Err()) + } + s.streamNotifications(ctx, id, notifications) + return nil + } +} + +func (s *TestkubeAPI) StreamTestWorkflowExecutionParallelStepNotificationsHandler() fiber.Handler { + return func(c *fiber.Ctx) error { + ctx := c.Context() + executionID := c.Params("executionID") + ref := c.Params("ref") + workerIndex := c.Params("workerIndex") + errPrefix := fmt.Sprintf("failed to stream test workflow execution parallel step '%s' instance '%s' notifications '%s'", + ref, workerIndex, executionID) + + // Fetch execution from database + execution, err := s.TestWorkflowResults.Get(ctx, executionID) + if err != nil { + return s.ClientError(c, errPrefix, err) + } + + reference := execution.GetParallelStepReference(ref) + if reference == "" { + return s.ClientError(c, errPrefix, errors.New("unknown parallel step for test workflow execution")) + } + + // Check for the logs + id := fmt.Sprintf("%s-%s-%s", execution.Id, reference, workerIndex) + notifications := s.ExecutionWorkerClient.Notifications(ctx, id, executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, + }) + if notifications.Err() != nil { + return s.BadRequest(c, errPrefix, "fetching notifications", notifications.Err()) + } + + s.streamNotifications(ctx, id, notifications) return nil } } @@ -121,6 +203,98 @@ func (s *TestkubeAPI) StreamTestWorkflowExecutionNotificationsWebSocketHandler() }) } +func (s *TestkubeAPI) StreamTestWorkflowExecutionServiceNotificationsWebSocketHandler() fiber.Handler { + return websocket.New(func(c *websocket.Conn) { + ctx, ctxCancel := context.WithCancel(context.Background()) + executionID := c.Params("executionID") + serviceName := c.Params("serviceName") + serviceIndex := c.Params("serviceIndex") + + // Stop reading when the WebSocket connection is already closed + originalClose := c.CloseHandler() + c.SetCloseHandler(func(code int, text string) error { + ctxCancel() + return originalClose(code, text) + }) + defer c.Conn.Close() + + // Fetch execution from database + execution, err := s.TestWorkflowResults.Get(ctx, executionID) + if err != nil { + return + } + + found := false + if execution.Workflow != nil && execution.Workflow.Spec != nil { + found = execution.Workflow.HasService(serviceName) + } + + if !found { + return + } + + // Check for the logs + id := fmt.Sprintf("%s-%s-%s", execution.Id, serviceName, serviceIndex) + notifications := s.ExecutionWorkerClient.Notifications(ctx, id, executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, + }) + if notifications.Err() != nil { + return + } + + for n := range notifications.Channel() { + _ = c.WriteJSON(n) + } + }) +} + +func (s *TestkubeAPI) StreamTestWorkflowExecutionParallelStepNotificationsWebSocketHandler() fiber.Handler { + return websocket.New(func(c *websocket.Conn) { + ctx, ctxCancel := context.WithCancel(context.Background()) + executionID := c.Params("executionID") + ref := c.Params("ref") + workerIndex := c.Params("workerIndex") + + // Stop reading when the WebSocket connection is already closed + originalClose := c.CloseHandler() + c.SetCloseHandler(func(code int, text string) error { + ctxCancel() + return originalClose(code, text) + }) + defer c.Conn.Close() + + // Fetch execution from database + execution, err := s.TestWorkflowResults.Get(ctx, executionID) + if err != nil { + return + } + + reference := execution.GetParallelStepReference(ref) + if reference == "" { + return + } + + // Check for the logs + id := fmt.Sprintf("%s-%s-%s", execution.Id, reference, workerIndex) + notifications := s.ExecutionWorkerClient.Notifications(ctx, id, executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, + }) + if notifications.Err() != nil { + return + } + + for n := range notifications.Channel() { + _ = c.WriteJSON(n) + } + }) +} + func (s *TestkubeAPI) ListTestWorkflowExecutionsHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to list test workflow executions" diff --git a/internal/config/config.go b/internal/config/config.go index 9c2d6fc83f0..d3a4d14fbcf 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -40,48 +40,50 @@ type Config struct { LogsStorage string `envconfig:"LOGS_STORAGE" default:""` WorkflowStorage string `envconfig:"WORKFLOW_STORAGE" default:"crd"` // WhitelistedContainers is a list of containers from which logs should be collected. - WhitelistedContainers []string `envconfig:"WHITELISTED_CONTAINERS" default:"init,logs,scraper"` - NatsEmbedded bool `envconfig:"NATS_EMBEDDED" default:"false"` - NatsEmbeddedStoreDir string `envconfig:"NATS_EMBEDDED_STORE_DIR" default:"/app/nats"` - NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"` - NatsSecure bool `envconfig:"NATS_SECURE" default:"false"` - NatsSkipVerify bool `envconfig:"NATS_SKIP_VERIFY" default:"false"` - NatsCertFile string `envconfig:"NATS_CERT_FILE" default:""` - NatsKeyFile string `envconfig:"NATS_KEY_FILE" default:""` - NatsCAFile string `envconfig:"NATS_CA_FILE" default:""` - NatsConnectTimeout time.Duration `envconfig:"NATS_CONNECT_TIMEOUT" default:"5s"` - JobServiceAccountName string `envconfig:"JOB_SERVICE_ACCOUNT_NAME" default:""` - JobTemplateFile string `envconfig:"JOB_TEMPLATE_FILE" default:""` - DisableTestTriggers bool `envconfig:"DISABLE_TEST_TRIGGERS" default:"false"` - TestkubeDefaultExecutors string `envconfig:"TESTKUBE_DEFAULT_EXECUTORS" default:""` - TestkubeEnabledExecutors string `envconfig:"TESTKUBE_ENABLED_EXECUTORS" default:""` - TestkubeTemplateJob string `envconfig:"TESTKUBE_TEMPLATE_JOB" default:""` - TestkubeContainerTemplateJob string `envconfig:"TESTKUBE_CONTAINER_TEMPLATE_JOB" default:""` - TestkubeContainerTemplateScraper string `envconfig:"TESTKUBE_CONTAINER_TEMPLATE_SCRAPER" default:""` - TestkubeContainerTemplatePVC string `envconfig:"TESTKUBE_CONTAINER_TEMPLATE_PVC" default:""` - TestkubeTemplateSlavePod string `envconfig:"TESTKUBE_TEMPLATE_SLAVE_POD" default:""` - TestkubeConfigDir string `envconfig:"TESTKUBE_CONFIG_DIR" default:"config"` - TestkubeAnalyticsEnabled bool `envconfig:"TESTKUBE_ANALYTICS_ENABLED" default:"false"` - TestkubeReadonlyExecutors bool `envconfig:"TESTKUBE_READONLY_EXECUTORS" default:"false"` - TestkubeNamespace string `envconfig:"TESTKUBE_NAMESPACE" default:"testkube"` - TestkubeProAPIKey string `envconfig:"TESTKUBE_PRO_API_KEY" default:""` - TestkubeProURL string `envconfig:"TESTKUBE_PRO_URL" default:""` - TestkubeProTLSInsecure bool `envconfig:"TESTKUBE_PRO_TLS_INSECURE" default:"false"` - TestkubeProWorkerCount int `envconfig:"TESTKUBE_PRO_WORKER_COUNT" default:"50"` - TestkubeProLogStreamWorkerCount int `envconfig:"TESTKUBE_PRO_LOG_STREAM_WORKER_COUNT" default:"25"` - TestkubeProWorkflowNotificationsWorkerCount int `envconfig:"TESTKUBE_PRO_WORKFLOW_NOTIFICATIONS_STREAM_WORKER_COUNT" default:"25"` - TestkubeProSkipVerify bool `envconfig:"TESTKUBE_PRO_SKIP_VERIFY" default:"false"` - TestkubeProEnvID string `envconfig:"TESTKUBE_PRO_ENV_ID" default:""` - TestkubeProOrgID string `envconfig:"TESTKUBE_PRO_ORG_ID" default:""` - TestkubeProMigrate string `envconfig:"TESTKUBE_PRO_MIGRATE" default:"false"` - TestkubeProConnectionTimeout int `envconfig:"TESTKUBE_PRO_CONNECTION_TIMEOUT" default:"10"` - TestkubeProCertFile string `envconfig:"TESTKUBE_PRO_CERT_FILE" default:""` - TestkubeProKeyFile string `envconfig:"TESTKUBE_PRO_KEY_FILE" default:""` - TestkubeProTLSSecret string `envconfig:"TESTKUBE_PRO_TLS_SECRET" default:""` - TestkubeProRunnerCustomCASecret string `envconfig:"TESTKUBE_PRO_RUNNER_CUSTOM_CA_SECRET" default:""` - TestkubeWatcherNamespaces string `envconfig:"TESTKUBE_WATCHER_NAMESPACES" default:""` - TestkubeRegistry string `envconfig:"TESTKUBE_REGISTRY" default:""` - TestkubePodStartTimeout time.Duration `envconfig:"TESTKUBE_POD_START_TIMEOUT" default:"30m"` + WhitelistedContainers []string `envconfig:"WHITELISTED_CONTAINERS" default:"init,logs,scraper"` + NatsEmbedded bool `envconfig:"NATS_EMBEDDED" default:"false"` + NatsEmbeddedStoreDir string `envconfig:"NATS_EMBEDDED_STORE_DIR" default:"/app/nats"` + NatsURI string `envconfig:"NATS_URI" default:"nats://localhost:4222"` + NatsSecure bool `envconfig:"NATS_SECURE" default:"false"` + NatsSkipVerify bool `envconfig:"NATS_SKIP_VERIFY" default:"false"` + NatsCertFile string `envconfig:"NATS_CERT_FILE" default:""` + NatsKeyFile string `envconfig:"NATS_KEY_FILE" default:""` + NatsCAFile string `envconfig:"NATS_CA_FILE" default:""` + NatsConnectTimeout time.Duration `envconfig:"NATS_CONNECT_TIMEOUT" default:"5s"` + JobServiceAccountName string `envconfig:"JOB_SERVICE_ACCOUNT_NAME" default:""` + JobTemplateFile string `envconfig:"JOB_TEMPLATE_FILE" default:""` + DisableTestTriggers bool `envconfig:"DISABLE_TEST_TRIGGERS" default:"false"` + TestkubeDefaultExecutors string `envconfig:"TESTKUBE_DEFAULT_EXECUTORS" default:""` + TestkubeEnabledExecutors string `envconfig:"TESTKUBE_ENABLED_EXECUTORS" default:""` + TestkubeTemplateJob string `envconfig:"TESTKUBE_TEMPLATE_JOB" default:""` + TestkubeContainerTemplateJob string `envconfig:"TESTKUBE_CONTAINER_TEMPLATE_JOB" default:""` + TestkubeContainerTemplateScraper string `envconfig:"TESTKUBE_CONTAINER_TEMPLATE_SCRAPER" default:""` + TestkubeContainerTemplatePVC string `envconfig:"TESTKUBE_CONTAINER_TEMPLATE_PVC" default:""` + TestkubeTemplateSlavePod string `envconfig:"TESTKUBE_TEMPLATE_SLAVE_POD" default:""` + TestkubeConfigDir string `envconfig:"TESTKUBE_CONFIG_DIR" default:"config"` + TestkubeAnalyticsEnabled bool `envconfig:"TESTKUBE_ANALYTICS_ENABLED" default:"false"` + TestkubeReadonlyExecutors bool `envconfig:"TESTKUBE_READONLY_EXECUTORS" default:"false"` + TestkubeNamespace string `envconfig:"TESTKUBE_NAMESPACE" default:"testkube"` + TestkubeProAPIKey string `envconfig:"TESTKUBE_PRO_API_KEY" default:""` + TestkubeProURL string `envconfig:"TESTKUBE_PRO_URL" default:""` + TestkubeProTLSInsecure bool `envconfig:"TESTKUBE_PRO_TLS_INSECURE" default:"false"` + TestkubeProWorkerCount int `envconfig:"TESTKUBE_PRO_WORKER_COUNT" default:"50"` + TestkubeProLogStreamWorkerCount int `envconfig:"TESTKUBE_PRO_LOG_STREAM_WORKER_COUNT" default:"25"` + TestkubeProWorkflowNotificationsWorkerCount int `envconfig:"TESTKUBE_PRO_WORKFLOW_NOTIFICATIONS_STREAM_WORKER_COUNT" default:"25"` + TestkubeProWorkflowServiceNotificationsWorkerCount int `envconfig:"TESTKUBE_PRO_WORKFLOW_SERVICE_NOTIFICATIONS_STREAM_WORKER_COUNT" default:"25"` + TestkubeProWorkflowParallelStepNotificationsWorkerCount int `envconfig:"TESTKUBE_PRO_WORKFLOW_PARALLEL_STEP_NOTIFICATIONS_STREAM_WORKER_COUNT" default:"25"` + TestkubeProSkipVerify bool `envconfig:"TESTKUBE_PRO_SKIP_VERIFY" default:"false"` + TestkubeProEnvID string `envconfig:"TESTKUBE_PRO_ENV_ID" default:""` + TestkubeProOrgID string `envconfig:"TESTKUBE_PRO_ORG_ID" default:""` + TestkubeProMigrate string `envconfig:"TESTKUBE_PRO_MIGRATE" default:"false"` + TestkubeProConnectionTimeout int `envconfig:"TESTKUBE_PRO_CONNECTION_TIMEOUT" default:"10"` + TestkubeProCertFile string `envconfig:"TESTKUBE_PRO_CERT_FILE" default:""` + TestkubeProKeyFile string `envconfig:"TESTKUBE_PRO_KEY_FILE" default:""` + TestkubeProTLSSecret string `envconfig:"TESTKUBE_PRO_TLS_SECRET" default:""` + TestkubeProRunnerCustomCASecret string `envconfig:"TESTKUBE_PRO_RUNNER_CUSTOM_CA_SECRET" default:""` + TestkubeWatcherNamespaces string `envconfig:"TESTKUBE_WATCHER_NAMESPACES" default:""` + TestkubeRegistry string `envconfig:"TESTKUBE_REGISTRY" default:""` + TestkubePodStartTimeout time.Duration `envconfig:"TESTKUBE_POD_START_TIMEOUT" default:"30m"` // TestkubeImageCredentialsCacheTTL is the duration for which the image pull credentials should be cached provided as a Go duration string. // If set to 0, the cache is disabled. TestkubeImageCredentialsCacheTTL time.Duration `envconfig:"TESTKUBE_IMAGE_CREDENTIALS_CACHE_TTL" default:"30m"` diff --git a/internal/config/procontext.go b/internal/config/procontext.go index aa3b090be81..ed8cf5d03b9 100644 --- a/internal/config/procontext.go +++ b/internal/config/procontext.go @@ -1,16 +1,18 @@ package config type ProContext struct { - APIKey string - URL string - TLSInsecure bool - WorkerCount int - LogStreamWorkerCount int - WorkflowNotificationsWorkerCount int - SkipVerify bool - EnvID string - OrgID string - Migrate string - ConnectionTimeout int - DashboardURI string + APIKey string + URL string + TLSInsecure bool + WorkerCount int + LogStreamWorkerCount int + WorkflowNotificationsWorkerCount int + WorkflowServiceNotificationsWorkerCount int + WorkflowParallelStepNotificationsWorkerCount int + SkipVerify bool + EnvID string + OrgID string + Migrate string + ConnectionTimeout int + DashboardURI string } diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index b612770d261..07178e4b5e7 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -26,8 +26,8 @@ import ( const ( clusterIDMeta = "cluster-id" cloudMigrateMeta = "migrate" - orgIdMeta = "environment-id" - envIdMeta = "organization-id" + orgIdMeta = "organization-id" + envIdMeta = "environment-id" healthcheckCommand = "healthcheck" dockerImageVersionMeta = "docker-image-version" ) @@ -55,6 +55,16 @@ type Agent struct { testWorkflowNotificationsResponseBuffer chan *cloud.TestWorkflowNotificationsResponse testWorkflowNotificationsFunc func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) + testWorkflowServiceNotificationsWorkerCount int + testWorkflowServiceNotificationsRequestBuffer chan *cloud.TestWorkflowServiceNotificationsRequest + testWorkflowServiceNotificationsResponseBuffer chan *cloud.TestWorkflowServiceNotificationsResponse + testWorkflowServiceNotificationsFunc func(ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) + + testWorkflowParallelStepNotificationsWorkerCount int + testWorkflowParallelStepNotificationsRequestBuffer chan *cloud.TestWorkflowParallelStepNotificationsRequest + testWorkflowParallelStepNotificationsResponseBuffer chan *cloud.TestWorkflowParallelStepNotificationsResponse + testWorkflowParallelStepNotificationsFunc func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) + events chan testkube.Event sendTimeout time.Duration receiveTimeout time.Duration @@ -73,6 +83,8 @@ func NewAgent(logger *zap.SugaredLogger, client cloud.TestKubeCloudAPIClient, logStreamFunc func(ctx context.Context, executionID string) (chan output.Output, error), workflowNotificationsFunc func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error), + workflowServiceNotificationsFunc func(ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error), + workflowParallelStepNotificationsFunc func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error), clusterID string, clusterName string, features featureflags.FeatureFlags, @@ -99,11 +111,19 @@ func NewAgent(logger *zap.SugaredLogger, testWorkflowNotificationsRequestBuffer: make(chan *cloud.TestWorkflowNotificationsRequest, bufferSizePerWorker*proContext.WorkflowNotificationsWorkerCount), testWorkflowNotificationsResponseBuffer: make(chan *cloud.TestWorkflowNotificationsResponse, bufferSizePerWorker*proContext.WorkflowNotificationsWorkerCount), testWorkflowNotificationsFunc: workflowNotificationsFunc, - clusterID: clusterID, - clusterName: clusterName, - features: features, - proContext: proContext, - dockerImageVersion: dockerImageVersion, + testWorkflowServiceNotificationsWorkerCount: proContext.WorkflowServiceNotificationsWorkerCount, + testWorkflowServiceNotificationsRequestBuffer: make(chan *cloud.TestWorkflowServiceNotificationsRequest, bufferSizePerWorker*proContext.WorkflowServiceNotificationsWorkerCount), + testWorkflowServiceNotificationsResponseBuffer: make(chan *cloud.TestWorkflowServiceNotificationsResponse, bufferSizePerWorker*proContext.WorkflowServiceNotificationsWorkerCount), + testWorkflowServiceNotificationsFunc: workflowServiceNotificationsFunc, + testWorkflowParallelStepNotificationsWorkerCount: proContext.WorkflowParallelStepNotificationsWorkerCount, + testWorkflowParallelStepNotificationsRequestBuffer: make(chan *cloud.TestWorkflowParallelStepNotificationsRequest, bufferSizePerWorker*proContext.WorkflowParallelStepNotificationsWorkerCount), + testWorkflowParallelStepNotificationsResponseBuffer: make(chan *cloud.TestWorkflowParallelStepNotificationsResponse, bufferSizePerWorker*proContext.WorkflowParallelStepNotificationsWorkerCount), + testWorkflowParallelStepNotificationsFunc: workflowParallelStepNotificationsFunc, + clusterID: clusterID, + clusterName: clusterName, + features: features, + proContext: proContext, + dockerImageVersion: dockerImageVersion, }, nil } @@ -151,6 +171,20 @@ func (ag *Agent) run(ctx context.Context) (err error) { return ag.runTestWorkflowNotificationsWorker(groupCtx, ag.testWorkflowNotificationsWorkerCount) }) + g.Go(func() error { + return ag.runTestWorkflowServiceNotificationsLoop(groupCtx) + }) + g.Go(func() error { + return ag.runTestWorkflowServiceNotificationsWorker(groupCtx, ag.testWorkflowServiceNotificationsWorkerCount) + }) + + g.Go(func() error { + return ag.runTestWorkflowParallelStepNotificationsLoop(groupCtx) + }) + g.Go(func() error { + return ag.runTestWorkflowParallelStepNotificationsWorker(groupCtx, ag.testWorkflowParallelStepNotificationsWorkerCount) + }) + err = g.Wait() return err diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index fb6c6d17418..8df8f8e888d 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -59,10 +59,13 @@ func TestCommandExecution(t *testing.T) { var logStreamFunc func(ctx context.Context, executionID string) (chan output.Output, error) var workflowNotificationsStreamFunc func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) + var workflowServiceNotificationsStreamFunc func(ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) + var workflowParallelStepNotificationsStreamFunc func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) logger, _ := zap.NewDevelopment() proContext := config.ProContext{APIKey: "api-key", WorkerCount: 5, LogStreamWorkerCount: 5, WorkflowNotificationsWorkerCount: 5} - agent, err := agent.NewAgent(logger.Sugar(), m, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") + agent, err := agent.NewAgent(logger.Sugar(), m, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, + workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") if err != nil { t.Fatal(err) } @@ -97,6 +100,18 @@ func (cs *CloudServer) GetTestWorkflowNotificationsStream(srv cloud.TestKubeClou return nil } +func (cs *CloudServer) GetTestWorkflowServiceNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamServer) error { + <-cs.ctx.Done() + + return nil +} + +func (cs *CloudServer) GetTestWorkflowParallelStepNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer) error { + <-cs.ctx.Done() + + return nil +} + func (cs *CloudServer) ExecuteAsync(srv cloud.TestKubeCloudAPI_ExecuteAsyncServer) error { md, ok := metadata.FromIncomingContext(srv.Context()) if !ok { diff --git a/pkg/agent/events_test.go b/pkg/agent/events_test.go index 663e06ac192..603c72e07af 100644 --- a/pkg/agent/events_test.go +++ b/pkg/agent/events_test.go @@ -56,9 +56,12 @@ func TestEventLoop(t *testing.T) { var logStreamFunc func(ctx context.Context, executionID string) (chan output.Output, error) var workflowNotificationsStreamFunc func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) + var workflowServiceNotificationsStreamFunc func(ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) + var workflowParallelStepNotificationsStreamFunc func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) proContext := config.ProContext{APIKey: "api-key", WorkerCount: 5, LogStreamWorkerCount: 5, WorkflowNotificationsWorkerCount: 5} - agent, err := agent.NewAgent(logger.Sugar(), nil, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") + agent, err := agent.NewAgent(logger.Sugar(), nil, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, + workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") assert.NoError(t, err) go func() { l, err := agent.Load() @@ -110,6 +113,18 @@ func (cws *CloudEventServer) GetTestWorkflowNotificationsStream(srv cloud.TestKu return nil } +func (cws *CloudEventServer) GetTestWorkflowServiceNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamServer) error { + <-cws.ctx.Done() + + return nil +} + +func (cws *CloudEventServer) GetTestWorkflowParallelStepNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer) error { + <-cws.ctx.Done() + + return nil +} + func (cws *CloudEventServer) Send(srv cloud.TestKubeCloudAPI_SendServer) error { md, ok := metadata.FromIncomingContext(srv.Context()) if !ok { diff --git a/pkg/agent/logs_test.go b/pkg/agent/logs_test.go index 0491467415c..4b66726f684 100644 --- a/pkg/agent/logs_test.go +++ b/pkg/agent/logs_test.go @@ -66,10 +66,13 @@ func TestLogStream(t *testing.T) { return ch, nil } var workflowNotificationsStreamFunc func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) + var workflowServiceNotificationsStreamFunc func(ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) + var workflowParallelStepNotificationsStreamFunc func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) logger, _ := zap.NewDevelopment() proContext := config.ProContext{APIKey: "api-key", WorkerCount: 5, LogStreamWorkerCount: 5, WorkflowNotificationsWorkerCount: 5} - agent, err := agent.NewAgent(logger.Sugar(), m, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") + agent, err := agent.NewAgent(logger.Sugar(), m, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, + workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") if err != nil { t.Fatal(err) } @@ -102,6 +105,16 @@ func (cs *CloudLogsServer) GetTestWorkflowNotificationsStream(srv cloud.TestKube return nil } +func (cs *CloudLogsServer) GetTestWorkflowServiceNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamServer) error { + <-cs.ctx.Done() + return nil +} + +func (cs *CloudLogsServer) GetTestWorkflowParallelStepNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer) error { + <-cs.ctx.Done() + return nil +} + func (cs *CloudLogsServer) GetLogsStream(srv cloud.TestKubeCloudAPI_GetLogsStreamServer) error { md, ok := metadata.FromIncomingContext(srv.Context()) if !ok { diff --git a/pkg/agent/testworkflows.go b/pkg/agent/testworkflows.go index 2001fa9debd..2df024c7602 100644 --- a/pkg/agent/testworkflows.go +++ b/pkg/agent/testworkflows.go @@ -7,19 +7,30 @@ import ( "math" "time" + "github.com/avast/retry-go/v4" "github.com/pkg/errors" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/encoding/gzip" + "google.golang.org/grpc/metadata" + "github.com/kubeshop/testkube/internal/common" agentclient "github.com/kubeshop/testkube/pkg/agent/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/executor/output" + "github.com/kubeshop/testkube/pkg/repository/testworkflow" "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/controller" + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/registry" ) const testWorkflowNotificationsRetryCount = 10 +var ( + logRetryDelay = 100 * time.Millisecond +) + func getTestWorkflowNotificationType(n testkube.TestWorkflowExecutionNotification) cloud.TestWorkflowNotificationType { if n.Result != nil { return cloud.TestWorkflowNotificationType_WORKFLOW_STREAM_RESULT @@ -31,6 +42,8 @@ func getTestWorkflowNotificationType(n testkube.TestWorkflowExecutionNotificatio func (ag *Agent) runTestWorkflowNotificationsLoop(ctx context.Context) error { ctx = agentclient.AddAPIKeyMeta(ctx, ag.apiKey) + ctx = metadata.AppendToOutgoingContext(ctx, envIdMeta, ag.proContext.EnvID) + ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, ag.proContext.OrgID) ag.logger.Infow("initiating workflow notifications streaming connection with Cloud API") // creates a new Stream from the client side. ctx is used for the lifetime of the stream. @@ -74,6 +87,100 @@ func (ag *Agent) runTestWorkflowNotificationsLoop(ctx context.Context) error { return err } +func (ag *Agent) runTestWorkflowServiceNotificationsLoop(ctx context.Context) error { + ctx = agentclient.AddAPIKeyMeta(ctx, ag.apiKey) + ctx = metadata.AppendToOutgoingContext(ctx, envIdMeta, ag.proContext.EnvID) + ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, ag.proContext.OrgID) + + ag.logger.Infow("initiating workflow service notifications streaming connection with Cloud API") + // creates a new Stream from the client side. ctx is used for the lifetime of the stream. + opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} + stream, err := ag.client.GetTestWorkflowServiceNotificationsStream(ctx, opts...) + if err != nil { + ag.logger.Errorf("failed to execute: %w", err) + return errors.Wrap(err, "failed to setup stream") + } + + // GRPC stream have special requirements for concurrency on SendMsg, and RecvMsg calls. + // Please check https://github.com/grpc/grpc-go/blob/master/Documentation/concurrency.md + g, groupCtx := errgroup.WithContext(ctx) + g.Go(func() error { + for { + cmd, err := ag.receiveTestWorkflowServiceNotificationsRequest(groupCtx, stream) + if err != nil { + return err + } + + ag.testWorkflowServiceNotificationsRequestBuffer <- cmd + } + }) + + g.Go(func() error { + for { + select { + case resp := <-ag.testWorkflowServiceNotificationsResponseBuffer: + err := ag.sendTestWorkflowServiceNotificationsResponse(groupCtx, stream, resp) + if err != nil { + return err + } + case <-groupCtx.Done(): + return groupCtx.Err() + } + } + }) + + err = g.Wait() + + return err +} + +func (ag *Agent) runTestWorkflowParallelStepNotificationsLoop(ctx context.Context) error { + ctx = agentclient.AddAPIKeyMeta(ctx, ag.apiKey) + ctx = metadata.AppendToOutgoingContext(ctx, envIdMeta, ag.proContext.EnvID) + ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, ag.proContext.OrgID) + + ag.logger.Infow("initiating workflow parallel step notifications streaming connection with Cloud API") + // creates a new Stream from the client side. ctx is used for the lifetime of the stream. + opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} + stream, err := ag.client.GetTestWorkflowParallelStepNotificationsStream(ctx, opts...) + if err != nil { + ag.logger.Errorf("failed to execute: %w", err) + return errors.Wrap(err, "failed to setup stream") + } + + // GRPC stream have special requirements for concurrency on SendMsg, and RecvMsg calls. + // Please check https://github.com/grpc/grpc-go/blob/master/Documentation/concurrency.md + g, groupCtx := errgroup.WithContext(ctx) + g.Go(func() error { + for { + cmd, err := ag.receiveTestWorkflowParallelStepNotificationsRequest(groupCtx, stream) + if err != nil { + return err + } + + ag.testWorkflowParallelStepNotificationsRequestBuffer <- cmd + } + }) + + g.Go(func() error { + for { + select { + case resp := <-ag.testWorkflowParallelStepNotificationsResponseBuffer: + err := ag.sendTestWorkflowParallelStepNotificationsResponse(groupCtx, stream, resp) + if err != nil { + return err + } + case <-groupCtx.Done(): + return groupCtx.Err() + } + } + }) + + err = g.Wait() + + return err +} + func (ag *Agent) runTestWorkflowNotificationsWorker(ctx context.Context, numWorkers int) error { g, groupCtx := errgroup.WithContext(ctx) for i := 0; i < numWorkers; i++ { @@ -102,6 +209,62 @@ func (ag *Agent) runTestWorkflowNotificationsWorker(ctx context.Context, numWork return g.Wait() } +func (ag *Agent) runTestWorkflowServiceNotificationsWorker(ctx context.Context, numWorkers int) error { + g, groupCtx := errgroup.WithContext(ctx) + for i := 0; i < numWorkers; i++ { + g.Go(func() error { + for { + select { + case req := <-ag.testWorkflowServiceNotificationsRequestBuffer: + if req.RequestType == cloud.TestWorkflowNotificationsRequestType_WORKFLOW_STREAM_HEALTH_CHECK { + ag.testWorkflowServiceNotificationsResponseBuffer <- &cloud.TestWorkflowServiceNotificationsResponse{ + StreamId: req.StreamId, + SeqNo: 0, + } + break + } + + err := ag.executeWorkflowServiceNotificationsRequest(groupCtx, req) + if err != nil { + ag.logger.Errorf("error executing workflow service notifications request: %s", err.Error()) + } + case <-groupCtx.Done(): + return groupCtx.Err() + } + } + }) + } + return g.Wait() +} + +func (ag *Agent) runTestWorkflowParallelStepNotificationsWorker(ctx context.Context, numWorkers int) error { + g, groupCtx := errgroup.WithContext(ctx) + for i := 0; i < numWorkers; i++ { + g.Go(func() error { + for { + select { + case req := <-ag.testWorkflowParallelStepNotificationsRequestBuffer: + if req.RequestType == cloud.TestWorkflowNotificationsRequestType_WORKFLOW_STREAM_HEALTH_CHECK { + ag.testWorkflowParallelStepNotificationsResponseBuffer <- &cloud.TestWorkflowParallelStepNotificationsResponse{ + StreamId: req.StreamId, + SeqNo: 0, + } + break + } + + err := ag.executeWorkflowParallelStepNotificationsRequest(groupCtx, req) + if err != nil { + ag.logger.Errorf("error executing workflow parallel step notifications request: %s", err.Error()) + } + case <-groupCtx.Done(): + return groupCtx.Err() + } + } + }) + } + return g.Wait() +} + func (ag *Agent) executeWorkflowNotificationsRequest(ctx context.Context, req *cloud.TestWorkflowNotificationsRequest) error { notificationsCh, err := ag.testWorkflowNotificationsFunc(ctx, req.ExecutionId) for i := 0; i < testWorkflowNotificationsRetryCount; i++ { @@ -110,7 +273,7 @@ func (ag *Agent) executeWorkflowNotificationsRequest(ctx context.Context, req *c // Cloud sometimes slow to insert execution or test // while WorkflowNotifications request from websockets comes in faster // so we retry up to testWorkflowNotificationsRetryCount times. - time.Sleep(100 * time.Millisecond) + time.Sleep(logRetryDelay) notificationsCh, err = ag.testWorkflowNotificationsFunc(ctx, req.ExecutionId) } } @@ -162,6 +325,136 @@ func (ag *Agent) executeWorkflowNotificationsRequest(ctx context.Context, req *c } } +func (ag *Agent) executeWorkflowServiceNotificationsRequest(ctx context.Context, req *cloud.TestWorkflowServiceNotificationsRequest) error { + notificationsCh, err := retry.DoWithData( + func() (<-chan testkube.TestWorkflowExecutionNotification, error) { + // We have a race condition here + // Cloud sometimes slow to start service + // while WorkflowNotifications request from websockets comes in faster + // so we retry up to wait till service pod is uo or execution is finished. + return ag.testWorkflowServiceNotificationsFunc(ctx, req.ExecutionId, req.ServiceName, int(req.ServiceIndex)) + }, + retry.DelayType(retry.FixedDelay), + retry.Delay(logRetryDelay), + retry.RetryIf(func(err error) bool { + return errors.Is(err, registry.ErrResourceNotFound) + }), + retry.UntilSucceeded(), + ) + + if err != nil { + message := fmt.Sprintf("cannot get service pod logs: %s", err.Error()) + ag.testWorkflowServiceNotificationsResponseBuffer <- &cloud.TestWorkflowServiceNotificationsResponse{ + StreamId: req.StreamId, + SeqNo: 0, + Type: cloud.TestWorkflowNotificationType_WORKFLOW_STREAM_ERROR, + Message: fmt.Sprintf("%s %s", time.Now().Format(controller.KubernetesLogTimeFormat), message), + } + return nil + } + + for { + var i uint32 + select { + case n, ok := <-notificationsCh: + if !ok { + return nil + } + t := getTestWorkflowNotificationType(n) + msg := &cloud.TestWorkflowServiceNotificationsResponse{ + StreamId: req.StreamId, + SeqNo: i, + Timestamp: n.Ts.Format(time.RFC3339Nano), + Ref: n.Ref, + Type: t, + } + if n.Result != nil { + m, _ := json.Marshal(n.Result) + msg.Message = string(m) + } else if n.Output != nil { + m, _ := json.Marshal(n.Output) + msg.Message = string(m) + } else { + msg.Message = n.Log + } + i++ + + select { + case ag.testWorkflowServiceNotificationsResponseBuffer <- msg: + case <-ctx.Done(): + return ctx.Err() + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (ag *Agent) executeWorkflowParallelStepNotificationsRequest(ctx context.Context, req *cloud.TestWorkflowParallelStepNotificationsRequest) error { + notificationsCh, err := retry.DoWithData( + func() (<-chan testkube.TestWorkflowExecutionNotification, error) { + // We have a race condition here + // Cloud sometimes slow to start parallel step + // while WorkflowNotifications request from websockets comes in faster + // so we retry up to wait till parallel step pod is uo or execution is finished. + return ag.testWorkflowParallelStepNotificationsFunc(ctx, req.ExecutionId, req.Ref, int(req.WorkerIndex)) + }, + retry.DelayType(retry.FixedDelay), + retry.Delay(logRetryDelay), + retry.RetryIf(func(err error) bool { + return errors.Is(err, registry.ErrResourceNotFound) + }), + retry.UntilSucceeded(), + ) + + if err != nil { + message := fmt.Sprintf("cannot get parallel step pod logs: %s", err.Error()) + ag.testWorkflowParallelStepNotificationsResponseBuffer <- &cloud.TestWorkflowParallelStepNotificationsResponse{ + StreamId: req.StreamId, + SeqNo: 0, + Type: cloud.TestWorkflowNotificationType_WORKFLOW_STREAM_ERROR, + Message: fmt.Sprintf("%s %s", time.Now().Format(controller.KubernetesLogTimeFormat), message), + } + return nil + } + + for { + var i uint32 + select { + case n, ok := <-notificationsCh: + if !ok { + return nil + } + t := getTestWorkflowNotificationType(n) + msg := &cloud.TestWorkflowParallelStepNotificationsResponse{ + StreamId: req.StreamId, + SeqNo: i, + Timestamp: n.Ts.Format(time.RFC3339Nano), + Ref: n.Ref, + Type: t, + } + if n.Result != nil { + m, _ := json.Marshal(n.Result) + msg.Message = string(m) + } else if n.Output != nil { + m, _ := json.Marshal(n.Output) + msg.Message = string(m) + } else { + msg.Message = n.Log + } + i++ + + select { + case ag.testWorkflowParallelStepNotificationsResponseBuffer <- msg: + case <-ctx.Done(): + return ctx.Err() + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + func (ag *Agent) receiveTestWorkflowNotificationsRequest(ctx context.Context, stream cloud.TestKubeCloudAPI_GetTestWorkflowNotificationsStreamClient) (*cloud.TestWorkflowNotificationsRequest, error) { respChan := make(chan testWorkflowNotificationsRequest, 1) go func() { @@ -191,6 +484,64 @@ type testWorkflowNotificationsRequest struct { err error } +func (ag *Agent) receiveTestWorkflowServiceNotificationsRequest(ctx context.Context, stream cloud.TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamClient) (*cloud.TestWorkflowServiceNotificationsRequest, error) { + respChan := make(chan testWorkflowServiceNotificationsRequest, 1) + go func() { + cmd, err := stream.Recv() + respChan <- testWorkflowServiceNotificationsRequest{resp: cmd, err: err} + }() + + var cmd *cloud.TestWorkflowServiceNotificationsRequest + select { + case resp := <-respChan: + cmd = resp.resp + err := resp.err + + if err != nil { + ag.logger.Errorf("agent stream receive: %v", err) + return nil, err + } + case <-ctx.Done(): + return nil, ctx.Err() + } + + return cmd, nil +} + +type testWorkflowServiceNotificationsRequest struct { + resp *cloud.TestWorkflowServiceNotificationsRequest + err error +} + +func (ag *Agent) receiveTestWorkflowParallelStepNotificationsRequest(ctx context.Context, stream cloud.TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamClient) (*cloud.TestWorkflowParallelStepNotificationsRequest, error) { + respChan := make(chan testWorkflowParallelStepNotificationsRequest, 1) + go func() { + cmd, err := stream.Recv() + respChan <- testWorkflowParallelStepNotificationsRequest{resp: cmd, err: err} + }() + + var cmd *cloud.TestWorkflowParallelStepNotificationsRequest + select { + case resp := <-respChan: + cmd = resp.resp + err := resp.err + + if err != nil { + ag.logger.Errorf("agent stream receive: %v", err) + return nil, err + } + case <-ctx.Done(): + return nil, ctx.Err() + } + + return cmd, nil +} + +type testWorkflowParallelStepNotificationsRequest struct { + resp *cloud.TestWorkflowParallelStepNotificationsRequest + err error +} + func (ag *Agent) sendTestWorkflowNotificationsResponse(ctx context.Context, stream cloud.TestKubeCloudAPI_GetTestWorkflowNotificationsStreamClient, resp *cloud.TestWorkflowNotificationsResponse) error { errChan := make(chan error, 1) go func() { @@ -215,3 +566,128 @@ func (ag *Agent) sendTestWorkflowNotificationsResponse(ctx context.Context, stre return errors.New("send response too slow") } } + +func (ag *Agent) sendTestWorkflowServiceNotificationsResponse(ctx context.Context, stream cloud.TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamClient, resp *cloud.TestWorkflowServiceNotificationsResponse) error { + errChan := make(chan error, 1) + go func() { + errChan <- stream.Send(resp) + close(errChan) + }() + + t := time.NewTimer(ag.sendTimeout) + select { + case err := <-errChan: + if !t.Stop() { + <-t.C + } + return err + case <-ctx.Done(): + if !t.Stop() { + <-t.C + } + + return ctx.Err() + case <-t.C: + return errors.New("send response too slow") + } +} + +func (ag *Agent) sendTestWorkflowParallelStepNotificationsResponse(ctx context.Context, stream cloud.TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamClient, resp *cloud.TestWorkflowParallelStepNotificationsResponse) error { + errChan := make(chan error, 1) + go func() { + errChan <- stream.Send(resp) + close(errChan) + }() + + t := time.NewTimer(ag.sendTimeout) + select { + case err := <-errChan: + if !t.Stop() { + <-t.C + } + return err + case <-ctx.Done(): + if !t.Stop() { + <-t.C + } + + return ctx.Err() + case <-t.C: + return errors.New("send response too slow") + } +} + +func GetTestWorkflowNotificationsStream(testWorkflowResultsRepository testworkflow.Repository, executionWorker executionworkertypes.Worker) func( + ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) { + return func(ctx context.Context, executionID string) (<-chan testkube.TestWorkflowExecutionNotification, error) { + execution, err := testWorkflowResultsRepository.Get(ctx, executionID) + if err != nil { + return nil, err + } + notifications := executionWorker.Notifications(ctx, execution.Id, executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + Signature: execution.Signature, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, + }) + if notifications.Err() != nil { + return nil, notifications.Err() + } + return notifications.Channel(), nil + } +} + +func GetTestWorkflowServiceNotificationsStream(testWorkflowResultsRepository testworkflow.Repository, executionWorker executionworkertypes.Worker) func( + ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) { + return func(ctx context.Context, executionID, serviceName string, serviceIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) { + execution, err := testWorkflowResultsRepository.Get(ctx, executionID) + if err != nil { + return nil, err + } + + if execution.Result != nil && execution.Result.IsFinished() { + return nil, errors.New("test workflow execution is finished") + } + + notifications := executionWorker.Notifications(ctx, fmt.Sprintf("%s-%s-%d", execution.Id, serviceName, serviceIndex), executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, + }) + if notifications.Err() != nil { + return nil, notifications.Err() + } + return notifications.Channel(), nil + } +} + +func GetTestWorkflowParallelStepNotificationsStream(testWorkflowResultsRepository testworkflow.Repository, executionWorker executionworkertypes.Worker) func( + ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) { + return func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) { + execution, err := testWorkflowResultsRepository.Get(ctx, executionID) + if err != nil { + return nil, err + } + + if execution.Result != nil && execution.Result.IsFinished() { + return nil, errors.New("test workflow execution is finished") + } + + notifications := executionWorker.Notifications(ctx, fmt.Sprintf("%s-%s-%d", execution.Id, ref, workerIndex), executionworkertypes.NotificationsOptions{ + Hints: executionworkertypes.Hints{ + Namespace: execution.Namespace, + ScheduledAt: common.Ptr(execution.ScheduledAt), + }, + }) + if notifications.Err() != nil { + return nil, notifications.Err() + } + return notifications.Channel(), nil + } +} + +func GetDeprecatedLogStream(ctx context.Context, executionID string) (chan output.Output, error) { + return nil, errors.New("deprecated features have been disabled") +} diff --git a/pkg/api/v1/client/interface.go b/pkg/api/v1/client/interface.go index 82f13573776..056844022e3 100644 --- a/pkg/api/v1/client/interface.go +++ b/pkg/api/v1/client/interface.go @@ -154,6 +154,8 @@ type TestWorkflowAPI interface { ExecuteTestWorkflows(selector string, request testkube.TestWorkflowExecutionRequest) ([]testkube.TestWorkflowExecution, error) GetTestWorkflowExecutionNotifications(id string) (chan testkube.TestWorkflowExecutionNotification, error) GetTestWorkflowExecutionLogs(id string) ([]byte, error) + GetTestWorkflowExecutionServiceNotifications(id, serviceName string, serviceIndex int) (chan testkube.TestWorkflowExecutionNotification, error) + GetTestWorkflowExecutionParallelStepNotifications(id, ref string, workerIndex int) (chan testkube.TestWorkflowExecutionNotification, error) } // TestWorkflowExecutionAPI describes test workflow api methods diff --git a/pkg/api/v1/client/testworkflow.go b/pkg/api/v1/client/testworkflow.go index d878f935651..e3313126272 100644 --- a/pkg/api/v1/client/testworkflow.go +++ b/pkg/api/v1/client/testworkflow.go @@ -146,6 +146,22 @@ func (c TestWorkflowClient) GetTestWorkflowExecutionNotifications(id string) (no return notifications, err } +// GetTestWorkflowExecutionServiceNotifications returns events stream from job pods, based on job pods logs +func (c TestWorkflowClient) GetTestWorkflowExecutionServiceNotifications(id, serviceName string, serviceIndex int) (notifications chan testkube.TestWorkflowExecutionNotification, err error) { + notifications = make(chan testkube.TestWorkflowExecutionNotification) + uri := c.testWorkflowTransport.GetURI("/test-workflow-executions/%s/notifications/services/%s/%d", id, serviceName, serviceIndex) + err = c.testWorkflowTransport.GetTestWorkflowExecutionNotifications(uri, notifications) + return notifications, err +} + +// GetTestWorkflowExecutionParallelStepNotifications returns events stream from job pods, based on job pods logs +func (c TestWorkflowClient) GetTestWorkflowExecutionParallelStepNotifications(id, ref string, workerIndex int) (notifications chan testkube.TestWorkflowExecutionNotification, err error) { + notifications = make(chan testkube.TestWorkflowExecutionNotification) + uri := c.testWorkflowTransport.GetURI("/test-workflow-executions/%s/notifications/parallel-steps/%s/%d", id, ref, workerIndex) + err = c.testWorkflowTransport.GetTestWorkflowExecutionNotifications(uri, notifications) + return notifications, err +} + // GetTestWorkflowExecution returns single test workflow execution by id func (c TestWorkflowClient) GetTestWorkflowExecution(id string) (testkube.TestWorkflowExecution, error) { uri := c.testWorkflowExecutionTransport.GetURI("/test-workflow-executions/%s", id) diff --git a/pkg/api/v1/testkube/model_test_workflow_execution_extended.go b/pkg/api/v1/testkube/model_test_workflow_execution_extended.go index dc1c4e07362..4b758c5abdc 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution_extended.go @@ -115,3 +115,18 @@ func (e *TestWorkflowExecution) InitializationError(header string, err error) { func (e *TestWorkflowExecution) FailedToInitialize() bool { return e.Result.Status != nil && *e.Result.Status == ABORTED_TestWorkflowStatus && e.Result.QueuedAt.IsZero() } + +func (e *TestWorkflowExecution) GetParallelStepReference(nameOrReference string) string { + if e == nil { + return "" + } + + for _, signature := range e.Signature { + ref := signature.GetParallelStepReference(nameOrReference) + if ref != "" { + return ref + } + } + + return "" +} diff --git a/pkg/api/v1/testkube/model_test_workflow_extended.go b/pkg/api/v1/testkube/model_test_workflow_extended.go index 2150f760305..846218c83e2 100644 --- a/pkg/api/v1/testkube/model_test_workflow_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_extended.go @@ -96,3 +96,22 @@ func (w TestWorkflow) GetLabels() map[string]string { func (w TestWorkflow) GetAnnotations() map[string]string { return w.Annotations } + +func (w TestWorkflow) HasService(name string) bool { + if w.Spec == nil { + return false + } + + steps := append(w.Spec.Setup, append(w.Spec.Steps, w.Spec.After...)...) + for _, step := range steps { + if step.HasService(name) { + return true + } + } + + if _, ok := w.Spec.Services[name]; ok { + return true + } + + return false +} diff --git a/pkg/api/v1/testkube/model_test_workflow_signature_extended.go b/pkg/api/v1/testkube/model_test_workflow_signature_extended.go index cd5979e1f68..9bd2ba6fc6d 100644 --- a/pkg/api/v1/testkube/model_test_workflow_signature_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_signature_extended.go @@ -1,5 +1,7 @@ package testkube +const parallelCategory = "Run in parallel" + func (s *TestWorkflowSignature) Label() string { if s.Name != "" { return s.Name @@ -14,3 +16,25 @@ func (s *TestWorkflowSignature) Sequence() []TestWorkflowSignature { } return result } + +func (s *TestWorkflowSignature) GetParallelStepReference(nameOrReference string) string { + if s.Category == parallelCategory && (nameOrReference == "" || s.Ref == nameOrReference) { + return s.Ref + } + + for _, child := range s.Children { + if s.Name == nameOrReference { + ref := child.GetParallelStepReference("") + if ref != "" { + return ref + } + } + + ref := child.GetParallelStepReference(nameOrReference) + if ref != "" { + return ref + } + } + + return "" +} diff --git a/pkg/api/v1/testkube/model_test_workflow_step_extended.go b/pkg/api/v1/testkube/model_test_workflow_step_extended.go index b8ddb9db83e..4dd28c868f5 100644 --- a/pkg/api/v1/testkube/model_test_workflow_step_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_step_extended.go @@ -65,3 +65,22 @@ func (w *TestWorkflowStep) GetTemplateRefs() []TestWorkflowTemplateRef { return templateRefs } + +func (w *TestWorkflowStep) HasService(name string) bool { + if w.Parallel != nil && w.Parallel.HasService(name) { + return true + } + + steps := append(w.Setup, w.Steps...) + for _, step := range steps { + if step.HasService(name) { + return true + } + } + + if _, ok := w.Services[name]; ok { + return true + } + + return false +} diff --git a/pkg/api/v1/testkube/model_test_workflow_step_parallel_extended.go b/pkg/api/v1/testkube/model_test_workflow_step_parallel_extended.go index 7ad342975e1..2a9ccadf86e 100644 --- a/pkg/api/v1/testkube/model_test_workflow_step_parallel_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_step_parallel_extended.go @@ -29,3 +29,18 @@ func (w *TestWorkflowStepParallel) GetTemplateRefs() []TestWorkflowTemplateRef { return templateRefs } + +func (w *TestWorkflowStepParallel) HasService(name string) bool { + steps := append(w.Setup, append(w.Steps, w.After...)...) + for _, step := range steps { + if step.HasService(name) { + return true + } + } + + if _, ok := w.Services[name]; ok { + return true + } + + return false +} diff --git a/pkg/cloud/service.pb.go b/pkg/cloud/service.pb.go index 12eabcdee49..5bb59b2773e 100644 --- a/pkg/cloud/service.pb.go +++ b/pkg/cloud/service.pb.go @@ -1076,6 +1076,338 @@ func (x *CredentialResponse) GetContent() []byte { return nil } +type TestWorkflowServiceNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreamId string `protobuf:"bytes,1,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + ExecutionId string `protobuf:"bytes,2,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + ServiceName string `protobuf:"bytes,3,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + ServiceIndex int32 `protobuf:"varint,4,opt,name=service_index,json=serviceIndex,proto3" json:"service_index,omitempty"` + RequestType TestWorkflowNotificationsRequestType `protobuf:"varint,5,opt,name=request_type,json=requestType,proto3,enum=cloud.TestWorkflowNotificationsRequestType" json:"request_type,omitempty"` +} + +func (x *TestWorkflowServiceNotificationsRequest) Reset() { + *x = TestWorkflowServiceNotificationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestWorkflowServiceNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestWorkflowServiceNotificationsRequest) ProtoMessage() {} + +func (x *TestWorkflowServiceNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestWorkflowServiceNotificationsRequest.ProtoReflect.Descriptor instead. +func (*TestWorkflowServiceNotificationsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{14} +} + +func (x *TestWorkflowServiceNotificationsRequest) GetStreamId() string { + if x != nil { + return x.StreamId + } + return "" +} + +func (x *TestWorkflowServiceNotificationsRequest) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *TestWorkflowServiceNotificationsRequest) GetServiceName() string { + if x != nil { + return x.ServiceName + } + return "" +} + +func (x *TestWorkflowServiceNotificationsRequest) GetServiceIndex() int32 { + if x != nil { + return x.ServiceIndex + } + return 0 +} + +func (x *TestWorkflowServiceNotificationsRequest) GetRequestType() TestWorkflowNotificationsRequestType { + if x != nil { + return x.RequestType + } + return TestWorkflowNotificationsRequestType_WORKFLOW_STREAM_LOG_MESSAGE +} + +type TestWorkflowServiceNotificationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreamId string `protobuf:"bytes,1,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + SeqNo uint32 `protobuf:"varint,2,opt,name=seq_no,json=seqNo,proto3" json:"seq_no,omitempty"` + Timestamp string `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Ref string `protobuf:"bytes,4,opt,name=ref,proto3" json:"ref,omitempty"` + Type TestWorkflowNotificationType `protobuf:"varint,5,opt,name=type,proto3,enum=cloud.TestWorkflowNotificationType" json:"type,omitempty"` + Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` // based on type: log/error = inline, others = serialized to JSON +} + +func (x *TestWorkflowServiceNotificationsResponse) Reset() { + *x = TestWorkflowServiceNotificationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestWorkflowServiceNotificationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestWorkflowServiceNotificationsResponse) ProtoMessage() {} + +func (x *TestWorkflowServiceNotificationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestWorkflowServiceNotificationsResponse.ProtoReflect.Descriptor instead. +func (*TestWorkflowServiceNotificationsResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{15} +} + +func (x *TestWorkflowServiceNotificationsResponse) GetStreamId() string { + if x != nil { + return x.StreamId + } + return "" +} + +func (x *TestWorkflowServiceNotificationsResponse) GetSeqNo() uint32 { + if x != nil { + return x.SeqNo + } + return 0 +} + +func (x *TestWorkflowServiceNotificationsResponse) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +func (x *TestWorkflowServiceNotificationsResponse) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +func (x *TestWorkflowServiceNotificationsResponse) GetType() TestWorkflowNotificationType { + if x != nil { + return x.Type + } + return TestWorkflowNotificationType_WORKFLOW_STREAM_ERROR +} + +func (x *TestWorkflowServiceNotificationsResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type TestWorkflowParallelStepNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreamId string `protobuf:"bytes,1,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + ExecutionId string `protobuf:"bytes,2,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + Ref string `protobuf:"bytes,3,opt,name=ref,proto3" json:"ref,omitempty"` + WorkerIndex int32 `protobuf:"varint,4,opt,name=worker_index,json=workerIndex,proto3" json:"worker_index,omitempty"` + RequestType TestWorkflowNotificationsRequestType `protobuf:"varint,5,opt,name=request_type,json=requestType,proto3,enum=cloud.TestWorkflowNotificationsRequestType" json:"request_type,omitempty"` +} + +func (x *TestWorkflowParallelStepNotificationsRequest) Reset() { + *x = TestWorkflowParallelStepNotificationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestWorkflowParallelStepNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestWorkflowParallelStepNotificationsRequest) ProtoMessage() {} + +func (x *TestWorkflowParallelStepNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestWorkflowParallelStepNotificationsRequest.ProtoReflect.Descriptor instead. +func (*TestWorkflowParallelStepNotificationsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{16} +} + +func (x *TestWorkflowParallelStepNotificationsRequest) GetStreamId() string { + if x != nil { + return x.StreamId + } + return "" +} + +func (x *TestWorkflowParallelStepNotificationsRequest) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *TestWorkflowParallelStepNotificationsRequest) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +func (x *TestWorkflowParallelStepNotificationsRequest) GetWorkerIndex() int32 { + if x != nil { + return x.WorkerIndex + } + return 0 +} + +func (x *TestWorkflowParallelStepNotificationsRequest) GetRequestType() TestWorkflowNotificationsRequestType { + if x != nil { + return x.RequestType + } + return TestWorkflowNotificationsRequestType_WORKFLOW_STREAM_LOG_MESSAGE +} + +type TestWorkflowParallelStepNotificationsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StreamId string `protobuf:"bytes,1,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + SeqNo uint32 `protobuf:"varint,2,opt,name=seq_no,json=seqNo,proto3" json:"seq_no,omitempty"` + Timestamp string `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Ref string `protobuf:"bytes,4,opt,name=ref,proto3" json:"ref,omitempty"` + Type TestWorkflowNotificationType `protobuf:"varint,5,opt,name=type,proto3,enum=cloud.TestWorkflowNotificationType" json:"type,omitempty"` + Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` // based on type: log/error = inline, others = serialized to JSON +} + +func (x *TestWorkflowParallelStepNotificationsResponse) Reset() { + *x = TestWorkflowParallelStepNotificationsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestWorkflowParallelStepNotificationsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestWorkflowParallelStepNotificationsResponse) ProtoMessage() {} + +func (x *TestWorkflowParallelStepNotificationsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestWorkflowParallelStepNotificationsResponse.ProtoReflect.Descriptor instead. +func (*TestWorkflowParallelStepNotificationsResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{17} +} + +func (x *TestWorkflowParallelStepNotificationsResponse) GetStreamId() string { + if x != nil { + return x.StreamId + } + return "" +} + +func (x *TestWorkflowParallelStepNotificationsResponse) GetSeqNo() uint32 { + if x != nil { + return x.SeqNo + } + return 0 +} + +func (x *TestWorkflowParallelStepNotificationsResponse) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +func (x *TestWorkflowParallelStepNotificationsResponse) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +func (x *TestWorkflowParallelStepNotificationsResponse) GetType() TestWorkflowNotificationType { + if x != nil { + return x.Type + } + return TestWorkflowNotificationType_WORKFLOW_STREAM_ERROR +} + +func (x *TestWorkflowParallelStepNotificationsResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + var File_proto_service_proto protoreflect.FileDescriptor var file_proto_service_proto_rawDesc = []byte{ @@ -1193,71 +1525,150 @@ var file_proto_service_proto_rawDesc = []byte{ 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2a, 0x48, 0x0a, 0x15, 0x4c, 0x6f, 0x67, 0x73, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, - 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x52, 0x45, - 0x41, 0x4d, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, - 0x01, 0x2a, 0x69, 0x0a, 0x24, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x57, 0x4f, 0x52, - 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, - 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x57, 0x4f, - 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x48, 0x45, - 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x01, 0x2a, 0x8a, 0x01, 0x0a, - 0x1c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, - 0x15, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x4f, 0x52, 0x4b, - 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x10, - 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, - 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, - 0x16, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, - 0x5f, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x10, 0x03, 0x2a, 0x4c, 0x0a, 0x06, 0x4f, 0x70, 0x63, - 0x6f, 0x64, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x46, 0x52, 0x41, 0x4d, - 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x46, 0x52, - 0x41, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, - 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x32, 0xd3, 0x04, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, - 0x4b, 0x75, 0x62, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x50, 0x49, 0x12, 0x3c, 0x0a, 0x07, - 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, - 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, - 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x36, 0x0a, 0x04, 0x53, 0x65, - 0x6e, 0x64, 0x12, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, - 0x63, 0x6b, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x28, 0x01, 0x12, 0x35, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x41, 0x73, 0x79, 0x6e, 0x63, 0x12, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x1a, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0d, - 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x19, 0x2e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x7b, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, - 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x28, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x27, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, - 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, - 0x01, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x78, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, - 0x09, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x81, 0x02, 0x0a, 0x27, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4e, 0x0a, 0x0c, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xe1, 0x01, 0x0a, 0x28, + 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, + 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0xf3, 0x01, 0x0a, 0x2c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, + 0x65, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4e, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x2d, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, + 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x48, + 0x0a, 0x15, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x52, 0x45, 0x41, + 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, + 0x17, 0x0a, 0x13, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, + 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x01, 0x2a, 0x69, 0x0a, 0x24, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x1f, 0x0a, 0x1b, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, + 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, + 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, + 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, + 0x4b, 0x10, 0x01, 0x2a, 0x8a, 0x01, 0x0a, 0x1c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, + 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, + 0x17, 0x0a, 0x13, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, + 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x57, 0x4f, 0x52, 0x4b, + 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x55, + 0x4c, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, + 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x10, 0x03, + 0x2a, 0x4c, 0x0a, 0x06, 0x4f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, + 0x53, 0x50, 0x45, 0x43, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, + 0x58, 0x54, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, + 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, + 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x32, 0x88, + 0x07, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, 0x4b, 0x75, 0x62, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, + 0x41, 0x50, 0x49, 0x12, 0x3c, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x16, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x36, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, 0x35, 0x0a, 0x04, 0x43, 0x61, 0x6c, + 0x6c, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x41, 0x0a, 0x0c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x41, 0x73, 0x79, 0x6e, 0x63, + 0x12, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, + 0x01, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, + 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, + 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x7b, 0x0a, + 0x22, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x12, 0x28, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x27, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x90, 0x01, 0x0a, 0x29, 0x47, + 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x2e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x9f, 0x01, + 0x0a, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x12, 0x34, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, + 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x33, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, + 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, + 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, + 0x42, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, 0x09, 0x70, 0x6b, 0x67, + 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1273,63 +1684,75 @@ func file_proto_service_proto_rawDescGZIP() []byte { } var file_proto_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_proto_service_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_proto_service_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_proto_service_proto_goTypes = []interface{}{ - (LogsStreamRequestType)(0), // 0: cloud.LogsStreamRequestType - (TestWorkflowNotificationsRequestType)(0), // 1: cloud.TestWorkflowNotificationsRequestType - (TestWorkflowNotificationType)(0), // 2: cloud.TestWorkflowNotificationType - (Opcode)(0), // 3: cloud.Opcode - (*LogsStreamRequest)(nil), // 4: cloud.LogsStreamRequest - (*LogsStreamResponse)(nil), // 5: cloud.LogsStreamResponse - (*CommandRequest)(nil), // 6: cloud.CommandRequest - (*CommandResponse)(nil), // 7: cloud.CommandResponse - (*ExecuteRequest)(nil), // 8: cloud.ExecuteRequest - (*TestWorkflowNotificationsRequest)(nil), // 9: cloud.TestWorkflowNotificationsRequest - (*TestWorkflowNotificationsResponse)(nil), // 10: cloud.TestWorkflowNotificationsResponse - (*ProContextResponse)(nil), // 11: cloud.ProContextResponse - (*Capability)(nil), // 12: cloud.Capability - (*HeaderValue)(nil), // 13: cloud.HeaderValue - (*ExecuteResponse)(nil), // 14: cloud.ExecuteResponse - (*WebsocketData)(nil), // 15: cloud.WebsocketData - (*CredentialRequest)(nil), // 16: cloud.CredentialRequest - (*CredentialResponse)(nil), // 17: cloud.CredentialResponse - nil, // 18: cloud.ExecuteRequest.HeadersEntry - nil, // 19: cloud.ExecuteResponse.HeadersEntry - (*structpb.Struct)(nil), // 20: google.protobuf.Struct - (*emptypb.Empty)(nil), // 21: google.protobuf.Empty + (LogsStreamRequestType)(0), // 0: cloud.LogsStreamRequestType + (TestWorkflowNotificationsRequestType)(0), // 1: cloud.TestWorkflowNotificationsRequestType + (TestWorkflowNotificationType)(0), // 2: cloud.TestWorkflowNotificationType + (Opcode)(0), // 3: cloud.Opcode + (*LogsStreamRequest)(nil), // 4: cloud.LogsStreamRequest + (*LogsStreamResponse)(nil), // 5: cloud.LogsStreamResponse + (*CommandRequest)(nil), // 6: cloud.CommandRequest + (*CommandResponse)(nil), // 7: cloud.CommandResponse + (*ExecuteRequest)(nil), // 8: cloud.ExecuteRequest + (*TestWorkflowNotificationsRequest)(nil), // 9: cloud.TestWorkflowNotificationsRequest + (*TestWorkflowNotificationsResponse)(nil), // 10: cloud.TestWorkflowNotificationsResponse + (*ProContextResponse)(nil), // 11: cloud.ProContextResponse + (*Capability)(nil), // 12: cloud.Capability + (*HeaderValue)(nil), // 13: cloud.HeaderValue + (*ExecuteResponse)(nil), // 14: cloud.ExecuteResponse + (*WebsocketData)(nil), // 15: cloud.WebsocketData + (*CredentialRequest)(nil), // 16: cloud.CredentialRequest + (*CredentialResponse)(nil), // 17: cloud.CredentialResponse + (*TestWorkflowServiceNotificationsRequest)(nil), // 18: cloud.TestWorkflowServiceNotificationsRequest + (*TestWorkflowServiceNotificationsResponse)(nil), // 19: cloud.TestWorkflowServiceNotificationsResponse + (*TestWorkflowParallelStepNotificationsRequest)(nil), // 20: cloud.TestWorkflowParallelStepNotificationsRequest + (*TestWorkflowParallelStepNotificationsResponse)(nil), // 21: cloud.TestWorkflowParallelStepNotificationsResponse + nil, // 22: cloud.ExecuteRequest.HeadersEntry + nil, // 23: cloud.ExecuteResponse.HeadersEntry + (*structpb.Struct)(nil), // 24: google.protobuf.Struct + (*emptypb.Empty)(nil), // 25: google.protobuf.Empty } var file_proto_service_proto_depIdxs = []int32{ 0, // 0: cloud.LogsStreamRequest.request_type:type_name -> cloud.LogsStreamRequestType - 20, // 1: cloud.CommandRequest.payload:type_name -> google.protobuf.Struct - 18, // 2: cloud.ExecuteRequest.headers:type_name -> cloud.ExecuteRequest.HeadersEntry + 24, // 1: cloud.CommandRequest.payload:type_name -> google.protobuf.Struct + 22, // 2: cloud.ExecuteRequest.headers:type_name -> cloud.ExecuteRequest.HeadersEntry 1, // 3: cloud.TestWorkflowNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType 2, // 4: cloud.TestWorkflowNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType 12, // 5: cloud.ProContextResponse.capabilities:type_name -> cloud.Capability - 19, // 6: cloud.ExecuteResponse.headers:type_name -> cloud.ExecuteResponse.HeadersEntry + 23, // 6: cloud.ExecuteResponse.headers:type_name -> cloud.ExecuteResponse.HeadersEntry 3, // 7: cloud.WebsocketData.opcode:type_name -> cloud.Opcode - 13, // 8: cloud.ExecuteRequest.HeadersEntry.value:type_name -> cloud.HeaderValue - 13, // 9: cloud.ExecuteResponse.HeadersEntry.value:type_name -> cloud.HeaderValue - 14, // 10: cloud.TestKubeCloudAPI.Execute:input_type -> cloud.ExecuteResponse - 15, // 11: cloud.TestKubeCloudAPI.Send:input_type -> cloud.WebsocketData - 6, // 12: cloud.TestKubeCloudAPI.Call:input_type -> cloud.CommandRequest - 14, // 13: cloud.TestKubeCloudAPI.ExecuteAsync:input_type -> cloud.ExecuteResponse - 5, // 14: cloud.TestKubeCloudAPI.GetLogsStream:input_type -> cloud.LogsStreamResponse - 10, // 15: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:input_type -> cloud.TestWorkflowNotificationsResponse - 21, // 16: cloud.TestKubeCloudAPI.GetProContext:input_type -> google.protobuf.Empty - 16, // 17: cloud.TestKubeCloudAPI.GetCredential:input_type -> cloud.CredentialRequest - 8, // 18: cloud.TestKubeCloudAPI.Execute:output_type -> cloud.ExecuteRequest - 21, // 19: cloud.TestKubeCloudAPI.Send:output_type -> google.protobuf.Empty - 7, // 20: cloud.TestKubeCloudAPI.Call:output_type -> cloud.CommandResponse - 8, // 21: cloud.TestKubeCloudAPI.ExecuteAsync:output_type -> cloud.ExecuteRequest - 4, // 22: cloud.TestKubeCloudAPI.GetLogsStream:output_type -> cloud.LogsStreamRequest - 9, // 23: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:output_type -> cloud.TestWorkflowNotificationsRequest - 11, // 24: cloud.TestKubeCloudAPI.GetProContext:output_type -> cloud.ProContextResponse - 17, // 25: cloud.TestKubeCloudAPI.GetCredential:output_type -> cloud.CredentialResponse - 18, // [18:26] is the sub-list for method output_type - 10, // [10:18] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 1, // 8: cloud.TestWorkflowServiceNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType + 2, // 9: cloud.TestWorkflowServiceNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType + 1, // 10: cloud.TestWorkflowParallelStepNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType + 2, // 11: cloud.TestWorkflowParallelStepNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType + 13, // 12: cloud.ExecuteRequest.HeadersEntry.value:type_name -> cloud.HeaderValue + 13, // 13: cloud.ExecuteResponse.HeadersEntry.value:type_name -> cloud.HeaderValue + 14, // 14: cloud.TestKubeCloudAPI.Execute:input_type -> cloud.ExecuteResponse + 15, // 15: cloud.TestKubeCloudAPI.Send:input_type -> cloud.WebsocketData + 6, // 16: cloud.TestKubeCloudAPI.Call:input_type -> cloud.CommandRequest + 14, // 17: cloud.TestKubeCloudAPI.ExecuteAsync:input_type -> cloud.ExecuteResponse + 5, // 18: cloud.TestKubeCloudAPI.GetLogsStream:input_type -> cloud.LogsStreamResponse + 10, // 19: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:input_type -> cloud.TestWorkflowNotificationsResponse + 19, // 20: cloud.TestKubeCloudAPI.GetTestWorkflowServiceNotificationsStream:input_type -> cloud.TestWorkflowServiceNotificationsResponse + 21, // 21: cloud.TestKubeCloudAPI.GetTestWorkflowParallelStepNotificationsStream:input_type -> cloud.TestWorkflowParallelStepNotificationsResponse + 25, // 22: cloud.TestKubeCloudAPI.GetProContext:input_type -> google.protobuf.Empty + 16, // 23: cloud.TestKubeCloudAPI.GetCredential:input_type -> cloud.CredentialRequest + 8, // 24: cloud.TestKubeCloudAPI.Execute:output_type -> cloud.ExecuteRequest + 25, // 25: cloud.TestKubeCloudAPI.Send:output_type -> google.protobuf.Empty + 7, // 26: cloud.TestKubeCloudAPI.Call:output_type -> cloud.CommandResponse + 8, // 27: cloud.TestKubeCloudAPI.ExecuteAsync:output_type -> cloud.ExecuteRequest + 4, // 28: cloud.TestKubeCloudAPI.GetLogsStream:output_type -> cloud.LogsStreamRequest + 9, // 29: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:output_type -> cloud.TestWorkflowNotificationsRequest + 18, // 30: cloud.TestKubeCloudAPI.GetTestWorkflowServiceNotificationsStream:output_type -> cloud.TestWorkflowServiceNotificationsRequest + 20, // 31: cloud.TestKubeCloudAPI.GetTestWorkflowParallelStepNotificationsStream:output_type -> cloud.TestWorkflowParallelStepNotificationsRequest + 11, // 32: cloud.TestKubeCloudAPI.GetProContext:output_type -> cloud.ProContextResponse + 17, // 33: cloud.TestKubeCloudAPI.GetCredential:output_type -> cloud.CredentialResponse + 24, // [24:34] is the sub-list for method output_type + 14, // [14:24] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_proto_service_proto_init() } @@ -1506,6 +1929,54 @@ func file_proto_service_proto_init() { return nil } } + file_proto_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowServiceNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowServiceNotificationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowParallelStepNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowParallelStepNotificationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1513,7 +1984,7 @@ func file_proto_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_service_proto_rawDesc, NumEnums: 4, - NumMessages: 16, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/cloud/service_grpc.pb.go b/pkg/cloud/service_grpc.pb.go index 5b329874fe6..7efe7686715 100644 --- a/pkg/cloud/service_grpc.pb.go +++ b/pkg/cloud/service_grpc.pb.go @@ -31,6 +31,8 @@ type TestKubeCloudAPIClient interface { ExecuteAsync(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_ExecuteAsyncClient, error) GetLogsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetLogsStreamClient, error) GetTestWorkflowNotificationsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetTestWorkflowNotificationsStreamClient, error) + GetTestWorkflowServiceNotificationsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamClient, error) + GetTestWorkflowParallelStepNotificationsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamClient, error) GetProContext(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ProContextResponse, error) GetCredential(ctx context.Context, in *CredentialRequest, opts ...grpc.CallOption) (*CredentialResponse, error) } @@ -210,6 +212,68 @@ func (x *testKubeCloudAPIGetTestWorkflowNotificationsStreamClient) Recv() (*Test return m, nil } +func (c *testKubeCloudAPIClient) GetTestWorkflowServiceNotificationsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[5], "/cloud.TestKubeCloudAPI/GetTestWorkflowServiceNotificationsStream", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamClient{stream} + return x, nil +} + +type TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamClient interface { + Send(*TestWorkflowServiceNotificationsResponse) error + Recv() (*TestWorkflowServiceNotificationsRequest, error) + grpc.ClientStream +} + +type testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamClient) Send(m *TestWorkflowServiceNotificationsResponse) error { + return x.ClientStream.SendMsg(m) +} + +func (x *testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamClient) Recv() (*TestWorkflowServiceNotificationsRequest, error) { + m := new(TestWorkflowServiceNotificationsRequest) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *testKubeCloudAPIClient) GetTestWorkflowParallelStepNotificationsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[6], "/cloud.TestKubeCloudAPI/GetTestWorkflowParallelStepNotificationsStream", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamClient{stream} + return x, nil +} + +type TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamClient interface { + Send(*TestWorkflowParallelStepNotificationsResponse) error + Recv() (*TestWorkflowParallelStepNotificationsRequest, error) + grpc.ClientStream +} + +type testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamClient) Send(m *TestWorkflowParallelStepNotificationsResponse) error { + return x.ClientStream.SendMsg(m) +} + +func (x *testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamClient) Recv() (*TestWorkflowParallelStepNotificationsRequest, error) { + m := new(TestWorkflowParallelStepNotificationsRequest) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func (c *testKubeCloudAPIClient) GetProContext(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ProContextResponse, error) { out := new(ProContextResponse) err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/GetProContext", in, out, opts...) @@ -240,6 +304,8 @@ type TestKubeCloudAPIServer interface { ExecuteAsync(TestKubeCloudAPI_ExecuteAsyncServer) error GetLogsStream(TestKubeCloudAPI_GetLogsStreamServer) error GetTestWorkflowNotificationsStream(TestKubeCloudAPI_GetTestWorkflowNotificationsStreamServer) error + GetTestWorkflowServiceNotificationsStream(TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamServer) error + GetTestWorkflowParallelStepNotificationsStream(TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer) error GetProContext(context.Context, *emptypb.Empty) (*ProContextResponse, error) GetCredential(context.Context, *CredentialRequest) (*CredentialResponse, error) mustEmbedUnimplementedTestKubeCloudAPIServer() @@ -267,6 +333,12 @@ func (UnimplementedTestKubeCloudAPIServer) GetLogsStream(TestKubeCloudAPI_GetLog func (UnimplementedTestKubeCloudAPIServer) GetTestWorkflowNotificationsStream(TestKubeCloudAPI_GetTestWorkflowNotificationsStreamServer) error { return status.Errorf(codes.Unimplemented, "method GetTestWorkflowNotificationsStream not implemented") } +func (UnimplementedTestKubeCloudAPIServer) GetTestWorkflowServiceNotificationsStream(TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetTestWorkflowServiceNotificationsStream not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) GetTestWorkflowParallelStepNotificationsStream(TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetTestWorkflowParallelStepNotificationsStream not implemented") +} func (UnimplementedTestKubeCloudAPIServer) GetProContext(context.Context, *emptypb.Empty) (*ProContextResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetProContext not implemented") } @@ -434,6 +506,58 @@ func (x *testKubeCloudAPIGetTestWorkflowNotificationsStreamServer) Recv() (*Test return m, nil } +func _TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TestKubeCloudAPIServer).GetTestWorkflowServiceNotificationsStream(&testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamServer{stream}) +} + +type TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStreamServer interface { + Send(*TestWorkflowServiceNotificationsRequest) error + Recv() (*TestWorkflowServiceNotificationsResponse, error) + grpc.ServerStream +} + +type testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamServer) Send(m *TestWorkflowServiceNotificationsRequest) error { + return x.ServerStream.SendMsg(m) +} + +func (x *testKubeCloudAPIGetTestWorkflowServiceNotificationsStreamServer) Recv() (*TestWorkflowServiceNotificationsResponse, error) { + m := new(TestWorkflowServiceNotificationsResponse) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TestKubeCloudAPIServer).GetTestWorkflowParallelStepNotificationsStream(&testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamServer{stream}) +} + +type TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer interface { + Send(*TestWorkflowParallelStepNotificationsRequest) error + Recv() (*TestWorkflowParallelStepNotificationsResponse, error) + grpc.ServerStream +} + +type testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamServer) Send(m *TestWorkflowParallelStepNotificationsRequest) error { + return x.ServerStream.SendMsg(m) +} + +func (x *testKubeCloudAPIGetTestWorkflowParallelStepNotificationsStreamServer) Recv() (*TestWorkflowParallelStepNotificationsResponse, error) { + m := new(TestWorkflowParallelStepNotificationsResponse) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func _TestKubeCloudAPI_GetProContext_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(emptypb.Empty) if err := dec(in); err != nil { @@ -520,6 +644,18 @@ var TestKubeCloudAPI_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, ClientStreams: true, }, + { + StreamName: "GetTestWorkflowServiceNotificationsStream", + Handler: _TestKubeCloudAPI_GetTestWorkflowServiceNotificationsStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "GetTestWorkflowParallelStepNotificationsStream", + Handler: _TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, }, Metadata: "proto/service.proto", } diff --git a/proto/service.proto b/proto/service.proto index 7ed7f0db957..b9ca13dafd1 100644 --- a/proto/service.proto +++ b/proto/service.proto @@ -16,6 +16,8 @@ service TestKubeCloudAPI { rpc ExecuteAsync(stream ExecuteResponse) returns (stream ExecuteRequest); rpc GetLogsStream(stream LogsStreamResponse) returns (stream LogsStreamRequest); rpc GetTestWorkflowNotificationsStream(stream TestWorkflowNotificationsResponse) returns (stream TestWorkflowNotificationsRequest); + rpc GetTestWorkflowServiceNotificationsStream(stream TestWorkflowServiceNotificationsResponse) returns (stream TestWorkflowServiceNotificationsRequest); + rpc GetTestWorkflowParallelStepNotificationsStream(stream TestWorkflowParallelStepNotificationsResponse) returns (stream TestWorkflowParallelStepNotificationsRequest); rpc GetProContext(google.protobuf.Empty) returns (ProContextResponse); rpc GetCredential(CredentialRequest) returns (CredentialResponse); } @@ -124,3 +126,37 @@ message CredentialRequest { message CredentialResponse { bytes content = 1; } + +message TestWorkflowServiceNotificationsRequest { + string stream_id = 1; + string execution_id = 2; + string service_name = 3; + int32 service_index = 4; + TestWorkflowNotificationsRequestType request_type = 5; +} + +message TestWorkflowServiceNotificationsResponse { + string stream_id = 1; + uint32 seq_no = 2; + string timestamp = 3; + string ref = 4; + TestWorkflowNotificationType type = 5; + string message = 6; // based on type: log/error = inline, others = serialized to JSON +} + +message TestWorkflowParallelStepNotificationsRequest { + string stream_id = 1; + string execution_id = 2; + string ref = 3; + int32 worker_index = 4; + TestWorkflowNotificationsRequestType request_type = 5; +} + +message TestWorkflowParallelStepNotificationsResponse { + string stream_id = 1; + uint32 seq_no = 2; + string timestamp = 3; + string ref = 4; + TestWorkflowNotificationType type = 5; + string message = 6; // based on type: log/error = inline, others = serialized to JSON +}