Files
tasks/tests/signal_workflow.go
Prathyush PVandGitHub 928d3aa339 Refactor tests.testDCClient as dynamicconfig.MemoryClient (#6337)
## What changed?
Refactor testDCClient from tests package as dynamicconfig.MemoryClient.

## Why?
This DC Client is useful for testing in other packages.

## How did you test it?
Existing unit tests

## Potential risks
None

## Documentation

## Is hotfix candidate?
No
2024-08-06 09:22:03 -07:00

1987 lines
72 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package tests
import (
"bytes"
"encoding/binary"
"fmt"
"strconv"
"strings"
"time"
"github.com/pborman/uuid"
commandpb "go.temporal.io/api/command/v1"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
filterpb "go.temporal.io/api/filter/v1"
historypb "go.temporal.io/api/history/v1"
"go.temporal.io/api/serviceerror"
taskqueuepb "go.temporal.io/api/taskqueue/v1"
"go.temporal.io/api/workflowservice/v1"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/timestamppb"
"go.temporal.io/server/common/convert"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/payload"
"go.temporal.io/server/common/payloads"
"go.temporal.io/server/common/rpc"
"go.temporal.io/server/service/history/consts"
)
func (s *FunctionalSuite) TestSignalWorkflow() {
id := "functional-signal-workflow-test"
wt := "functional-signal-workflow-test-type"
tl := "functional-signal-workflow-test-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
// Send a signal to non-exist workflow
header := &commonpb.Header{
Fields: map[string]*commonpb.Payload{"signal header key": payload.EncodeString("signal header value")},
}
_, err0 := s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: uuid.New(),
},
SignalName: "failed signal",
Input: nil,
Identity: identity,
Header: header,
})
s.NotNil(err0)
s.IsType(&serviceerror.NotFound{}, err0)
// Start workflow execution
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
// workflow logic
workflowComplete := false
activityScheduled := false
activityData := int32(1)
var signalEvent *historypb.HistoryEvent
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if !activityScheduled {
activityScheduled = true
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityData))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: strconv.Itoa(1),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(2 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
} else if task.PreviousStartedEventId > 0 {
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
return []*commandpb.Command{}, nil
}
}
}
workflowComplete = true
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
// activity handler
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Make first command to schedule activity
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// Send first signal using RunID
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
_, err = s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: signalName,
Input: signalInput,
Identity: identity,
Header: header,
})
s.NoError(err)
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
s.ProtoEqual(header, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Header)
// Send another signal without RunID
signalName = "another signal"
signalInput = payloads.EncodeString("another signal input")
_, err = s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
},
SignalName: signalName,
Input: signalInput,
Identity: identity,
})
s.NoError(err)
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
// Terminate workflow execution
_, err = s.client.TerminateWorkflowExecution(NewContext(), &workflowservice.TerminateWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
},
Reason: "test signal",
Details: nil,
Identity: identity,
})
s.NoError(err)
// Send signal to terminated workflow
_, err = s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: "failed signal 1",
Input: nil,
Identity: identity,
})
s.NotNil(err)
s.IsType(&serviceerror.NotFound{}, err)
}
func (s *FunctionalSuite) TestSignalWorkflow_DuplicateRequest() {
id := "functional-signal-workflow-test-duplicate"
wt := "functional-signal-workflow-test-duplicate-type"
tl := "functional-signal-workflow-test-duplicate-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
// Start workflow execution
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
// workflow logic
workflowComplete := false
activityScheduled := false
activityData := int32(1)
var signalEvent *historypb.HistoryEvent
numOfSignaledEvent := 0
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if !activityScheduled {
activityScheduled = true
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityData))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: strconv.Itoa(1),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(2 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
} else if task.PreviousStartedEventId > 0 {
numOfSignaledEvent = 0
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
numOfSignaledEvent++
}
}
return []*commandpb.Command{}, nil
}
workflowComplete = true
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
// activity handler
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Make first command to schedule activity
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// Send first signal
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
requestID := uuid.New()
signalReqest := &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: signalName,
Input: signalInput,
Identity: identity,
RequestId: requestID,
}
_, err = s.client.SignalWorkflowExecution(NewContext(), signalReqest)
s.NoError(err)
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
s.Equal(1, numOfSignaledEvent)
// Send another signal with same request id
_, err = s.client.SignalWorkflowExecution(NewContext(), signalReqest)
s.NoError(err)
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(0, numOfSignaledEvent)
}
func (s *FunctionalSuite) TestSignalExternalWorkflowCommand() {
id := "functional-signal-external-workflow-test"
wt := "functional-signal-external-workflow-test-type"
tl := "functional-signal-external-workflow-test-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
foreignRequest := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.foreignNamespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we2, err0 := s.client.StartWorkflowExecution(NewContext(), foreignRequest)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution on foreign Namespace", tag.WorkflowNamespace(s.foreignNamespace), tag.WorkflowRunID(we2.RunId))
activityCount := int32(1)
activityCounter := int32(0)
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
signalHeader := &commonpb.Header{
Fields: map[string]*commonpb.Payload{"signal header key": payload.EncodeString("signal header value")},
}
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if activityCounter < activityCount {
activityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(activityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_SignalExternalWorkflowExecutionCommandAttributes{SignalExternalWorkflowExecutionCommandAttributes: &commandpb.SignalExternalWorkflowExecutionCommandAttributes{
Namespace: s.foreignNamespace,
Execution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we2.GetRunId(),
},
SignalName: signalName,
Input: signalInput,
Header: signalHeader,
}},
}}, nil
}
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
workflowComplete := false
foreignActivityCount := int32(1)
foreignActivityCounter := int32(0)
var signalEvent *historypb.HistoryEvent
foreignwtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if foreignActivityCounter < foreignActivityCount {
foreignActivityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, foreignActivityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(foreignActivityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
} else if task.PreviousStartedEventId > 0 {
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
return []*commandpb.Command{}, nil
}
}
}
workflowComplete = true
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
foreignPoller := &TaskPoller{
Client: s.client,
Namespace: s.foreignNamespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: foreignwtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Start both current and foreign workflows to make some progress.
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
_, err = foreignPoller.PollAndProcessWorkflowTask()
s.Logger.Info("foreign PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
err = foreignPoller.PollAndProcessActivityTask(false)
s.Logger.Info("foreign PollAndProcessActivityTask", tag.Error(err))
s.NoError(err)
// Signal the foreign workflow with this command.
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// in source workflow
var historyEvents []*historypb.HistoryEvent
CheckHistoryLoopForSignalSent:
for i := 1; i < 10; i++ {
historyEvents = s.getHistory(s.namespace, &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
})
signalRequestedEvent := historyEvents[len(historyEvents)-2]
if signalRequestedEvent.GetEventType() != enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED {
s.Logger.Info("Signal still not sent")
time.Sleep(100 * time.Millisecond)
continue CheckHistoryLoopForSignalSent
}
break
}
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 ActivityTaskScheduled
6 ActivityTaskTimedOut
7 WorkflowTaskScheduled
8 WorkflowTaskStarted
9 WorkflowTaskCompleted
10 SignalExternalWorkflowExecutionInitiated
11 ExternalWorkflowExecutionSignaled {"InitiatedEventId":10,"WorkflowExecution":{"RunId":"%s","WorkflowId":"%s"}}
12 WorkflowTaskScheduled`, we2.RunId, id), historyEvents)
// Process signal in workflow for foreign workflow
_, err = foreignPoller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.NotNil(signalEvent)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.ProtoEqual(signalHeader, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Header)
s.Equal("history-service", signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
}
func (s *FunctionalSuite) TestSignalWorkflow_Cron_NoWorkflowTaskCreated() {
id := "functional-signal-workflow-test-cron"
wt := "functional-signal-workflow-test-cron-type"
tl := "functional-signal-workflow-test-cron-taskqueue"
identity := "worker1"
cronSpec := "@every 2s"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
// Start workflow execution
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
CronSchedule: cronSpec,
}
now := time.Now().UTC()
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
// Send first signal using RunID
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
_, err := s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: signalName,
Input: signalInput,
Identity: identity,
})
s.NoError(err)
// workflow logic
var workflowTaskDelay time.Duration
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
workflowTaskDelay = time.Now().UTC().Sub(now)
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: s.Logger,
T: s.T(),
}
// Make first command to schedule activity
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.True(workflowTaskDelay > time.Second*2)
}
func (s *FunctionalSuite) TestSignalWorkflow_NoWorkflowTaskCreated() {
id := "functional-signal-workflow-test-skip-wft"
wt := "functional-signal-workflow-test-skip-wft-type"
tl := "functional-signal-workflow-test-skip-wft-taskqueue"
identity := "worker1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
// Start workflow execution
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
workflowComplete := false
// workflow logic
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if !workflowComplete {
workflowComplete = true
return []*commandpb.Command{}, nil
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: s.Logger,
T: s.T(),
}
// process start task
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// Send first signal which should NOT generate a new wft
_, err = s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: "signal without task",
Input: nil,
Identity: identity,
SkipGenerateWorkflowTask: true,
})
s.NoError(err)
// Send second signal which should generate a new wft
_, err = s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: "signal with task",
Input: nil,
Identity: identity,
SkipGenerateWorkflowTask: false,
})
s.NoError(err)
// process signal and complete workflow
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
historyEvents := s.getHistory(s.namespace, &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
})
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionSignaled
6 WorkflowExecutionSignaled
7 WorkflowTaskScheduled
8 WorkflowTaskStarted
9 WorkflowTaskCompleted
10 WorkflowExecutionCompleted`, historyEvents)
}
func (s *FunctionalSuite) TestSignalWorkflow_WorkflowCloseAttempted() {
id := "functional-signal-workflow-workflow-close-attempted-test"
wt := "functional-signal-workflow-workflow-close-attempted-test-type"
tl := "functional-signal-workflow-workflow-close-attempted-test-taskqueue"
identity := "worker1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
we, err := s.client.StartWorkflowExecution(NewContext(), &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(3 * time.Second),
Identity: identity,
})
s.NoError(err)
attemptCount := 1
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if attemptCount == 1 {
_, err := s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: "buffered-signal",
Identity: identity,
RequestId: uuid.New(),
})
s.NoError(err)
}
if attemptCount == 2 {
ctx, _ := rpc.NewContextWithTimeoutAndVersionHeaders(time.Second)
_, err := s.client.SignalWorkflowExecution(ctx, &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
},
SignalName: "rejected-signal",
Identity: identity,
RequestId: uuid.New(),
})
s.Error(err)
s.Error(consts.ErrWorkflowClosing, err)
}
attemptCount++
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: s.Logger,
T: s.T(),
}
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.Error(err)
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
}
func (s *FunctionalSuite) TestSignalExternalWorkflowCommand_WithoutRunID() {
id := "functional-signal-external-workflow-test-without-run-id"
wt := "functional-signal-external-workflow-test-without-run-id-type"
tl := "functional-signal-external-workflow-test-without-run-id-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
foreignRequest := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.foreignNamespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we2, err0 := s.client.StartWorkflowExecution(NewContext(), foreignRequest)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution on foreign Namespace", tag.WorkflowNamespace(s.foreignNamespace), tag.WorkflowRunID(we2.RunId))
activityCount := int32(1)
activityCounter := int32(0)
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if activityCounter < activityCount {
activityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(activityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_SignalExternalWorkflowExecutionCommandAttributes{SignalExternalWorkflowExecutionCommandAttributes: &commandpb.SignalExternalWorkflowExecutionCommandAttributes{
Namespace: s.foreignNamespace,
Execution: &commonpb.WorkflowExecution{
WorkflowId: id,
// No RunID in command
},
SignalName: signalName,
Input: signalInput,
}},
}}, nil
}
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
workflowComplete := false
foreignActivityCount := int32(1)
foreignActivityCounter := int32(0)
var signalEvent *historypb.HistoryEvent
foreignwtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if foreignActivityCounter < foreignActivityCount {
foreignActivityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, foreignActivityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(foreignActivityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
} else if task.PreviousStartedEventId > 0 {
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
return []*commandpb.Command{}, nil
}
}
}
workflowComplete = true
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
foreignPoller := &TaskPoller{
Client: s.client,
Namespace: s.foreignNamespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: foreignwtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Start both current and foreign workflows to make some progress.
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
_, err = foreignPoller.PollAndProcessWorkflowTask()
s.Logger.Info("foreign PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
err = foreignPoller.PollAndProcessActivityTask(false)
s.Logger.Info("foreign PollAndProcessActivityTask", tag.Error(err))
s.NoError(err)
// Signal the foreign workflow with this command.
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// in source workflow
var historyEvents []*historypb.HistoryEvent
CheckHistoryLoopForSignalSent:
for i := 1; i < 10; i++ {
historyEvents = s.getHistory(s.namespace, &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
})
signalRequestedEvent := historyEvents[len(historyEvents)-2]
if signalRequestedEvent.GetEventType() != enumspb.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED {
s.Logger.Info("Signal still not sent")
time.Sleep(100 * time.Millisecond)
continue CheckHistoryLoopForSignalSent
}
break
}
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 ActivityTaskScheduled
6 ActivityTaskTimedOut
7 WorkflowTaskScheduled
8 WorkflowTaskStarted
9 WorkflowTaskCompleted
10 SignalExternalWorkflowExecutionInitiated
11 ExternalWorkflowExecutionSignaled {"InitiatedEventId":10,"WorkflowExecution":{"RunId":"","WorkflowId":"%s"}}
12 WorkflowTaskScheduled`, id), historyEvents)
// Process signal in workflow for foreign workflow
_, err = foreignPoller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal("history-service", signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
}
func (s *FunctionalSuite) TestSignalExternalWorkflowCommand_UnKnownTarget() {
id := "functional-signal-unknown-workflow-command-test"
wt := "functional-signal-unknown-workflow-command-test-type"
tl := "functional-signal-unknown-workflow-command-test-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
activityCount := int32(1)
activityCounter := int32(0)
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if activityCounter < activityCount {
activityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(activityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_SignalExternalWorkflowExecutionCommandAttributes{SignalExternalWorkflowExecutionCommandAttributes: &commandpb.SignalExternalWorkflowExecutionCommandAttributes{
Namespace: s.foreignNamespace,
Execution: &commonpb.WorkflowExecution{
WorkflowId: "workflow_not_exist",
RunId: we.GetRunId(),
},
SignalName: signalName,
Input: signalInput,
}},
}}, nil
}
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Start workflows to make some progress.
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// Signal the foreign workflow with this command.
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
var historyEvents []*historypb.HistoryEvent
CheckHistoryLoopForCancelSent:
for i := 1; i < 10; i++ {
historyEvents = s.getHistory(s.namespace, &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
})
signalFailedEvent := historyEvents[len(historyEvents)-2]
if signalFailedEvent.GetEventType() != enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED {
s.Logger.Info("Cancellaton not cancelled yet")
time.Sleep(100 * time.Millisecond)
continue CheckHistoryLoopForCancelSent
}
break
}
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 ActivityTaskScheduled
6 ActivityTaskTimedOut
7 WorkflowTaskScheduled
8 WorkflowTaskStarted
9 WorkflowTaskCompleted
10 SignalExternalWorkflowExecutionInitiated
11 SignalExternalWorkflowExecutionFailed {"InitiatedEventId":10,"WorkflowExecution":{"RunId":"%s","WorkflowId":"workflow_not_exist"}}
12 WorkflowTaskScheduled`, we.RunId), historyEvents)
}
func (s *FunctionalSuite) TestSignalExternalWorkflowCommand_SignalSelf() {
id := "functional-signal-self-workflow-command-test"
wt := "functional-signal-self-workflow-command-test-type"
tl := "functional-signal-self-workflow-command-test-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
activityCount := int32(1)
activityCounter := int32(0)
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if activityCounter < activityCount {
activityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(activityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_SignalExternalWorkflowExecutionCommandAttributes{SignalExternalWorkflowExecutionCommandAttributes: &commandpb.SignalExternalWorkflowExecutionCommandAttributes{
Namespace: s.namespace,
Execution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.GetRunId(),
},
SignalName: signalName,
Input: signalInput,
}},
}}, nil
}
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Start workflows to make some progress.
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// Signal the foreign workflow with this command.
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
var historyEvents []*historypb.HistoryEvent
CheckHistoryLoopForCancelSent:
for i := 1; i < 10; i++ {
historyEvents = s.getHistory(s.namespace, &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we.RunId,
})
signalFailedEvent := historyEvents[len(historyEvents)-2]
if signalFailedEvent.GetEventType() != enumspb.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED {
s.Logger.Info("Cancellaton not cancelled yet")
time.Sleep(100 * time.Millisecond)
continue CheckHistoryLoopForCancelSent
}
break
}
s.EqualHistoryEvents(fmt.Sprintf(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 ActivityTaskScheduled
6 ActivityTaskTimedOut
7 WorkflowTaskScheduled
8 WorkflowTaskStarted
9 WorkflowTaskCompleted
10 SignalExternalWorkflowExecutionInitiated
11 SignalExternalWorkflowExecutionFailed {"InitiatedEventId":10,"WorkflowExecution":{"RunId":"%s","WorkflowId":"%s"}}
12 WorkflowTaskScheduled`, we.RunId, id), historyEvents)
}
func (s *FunctionalSuite) TestSignalWithStartWorkflow() {
id := "functional-signal-with-start-workflow-test"
wt := "functional-signal-with-start-workflow-test-type"
tl := "functional-signal-with-start-workflow-test-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
header := &commonpb.Header{
Fields: map[string]*commonpb.Payload{"tracing": payload.EncodeString("sample data")},
}
// Start a workflow
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
// workflow logic
workflowComplete := false
activityScheduled := false
activityData := int32(1)
newWorkflowStarted := false
var signalEvent, startedEvent *historypb.HistoryEvent
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if !activityScheduled {
activityScheduled = true
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityData))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: strconv.Itoa(1),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(2 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
} else if task.PreviousStartedEventId > 0 {
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
return []*commandpb.Command{}, nil
}
}
} else if newWorkflowStarted {
newWorkflowStarted = false
signalEvent = nil
startedEvent = nil
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
}
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED {
startedEvent = event
}
}
if signalEvent != nil && startedEvent != nil {
return []*commandpb.Command{}, nil
}
}
workflowComplete = true
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
// activity handler
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Make first command to schedule activity
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
// Send a signal
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
wfIDReusePolicy := enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE
sRequest := &workflowservice.SignalWithStartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
Header: header,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
SignalName: signalName,
SignalInput: signalInput,
Identity: identity,
WorkflowIdReusePolicy: wfIDReusePolicy,
}
resp, err := s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
s.False(resp.Started)
s.Equal(we.GetRunId(), resp.GetRunId())
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
// Terminate workflow execution
_, err = s.client.TerminateWorkflowExecution(NewContext(), &workflowservice.TerminateWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
},
Reason: "test signal",
Details: nil,
Identity: identity,
})
s.NoError(err)
// Send signal to terminated workflow
signalName = "signal to terminate"
signalInput = payloads.EncodeString("signal to terminate input")
sRequest.SignalName = signalName
sRequest.SignalInput = signalInput
sRequest.WorkflowId = id
resp, err = s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
s.True(resp.Started)
s.NotNil(resp.GetRunId())
s.NotEqual(we.GetRunId(), resp.GetRunId())
newWorkflowStarted = true
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
s.True(startedEvent != nil)
s.ProtoEqual(header, startedEvent.GetWorkflowExecutionStartedEventAttributes().Header)
// Send signal to not existed workflow
id = "functional-signal-with-start-workflow-test-non-exist"
signalName = "signal to non exist"
signalInput = payloads.EncodeString("signal to non exist input")
sRequest.SignalName = signalName
sRequest.SignalInput = signalInput
sRequest.WorkflowId = id
resp, err = s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
s.NotNil(resp.GetRunId())
s.True(resp.Started)
newWorkflowStarted = true
// Process signal in workflow
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.False(workflowComplete)
s.True(signalEvent != nil)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
listOpenRequest := &workflowservice.ListOpenWorkflowExecutionsRequest{
Namespace: s.namespace,
MaximumPageSize: 100,
StartTimeFilter: &filterpb.StartTimeFilter{
EarliestTime: nil,
LatestTime: timestamppb.New(time.Now().UTC()),
},
Filters: &workflowservice.ListOpenWorkflowExecutionsRequest_ExecutionFilter{
ExecutionFilter: &filterpb.WorkflowExecutionFilter{
WorkflowId: id,
},
},
}
// Assert visibility is correct
s.Eventually(
func() bool {
listResp, err := s.client.ListOpenWorkflowExecutions(NewContext(), listOpenRequest)
s.NoError(err)
return len(listResp.Executions) == 1
},
waitForESToSettle,
100*time.Millisecond,
)
// Terminate workflow execution and assert visibility is correct
_, err = s.client.TerminateWorkflowExecution(NewContext(), &workflowservice.TerminateWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
},
Reason: "kill workflow",
Details: nil,
Identity: identity,
})
s.NoError(err)
s.Eventually(
func() bool {
listResp, err := s.client.ListOpenWorkflowExecutions(NewContext(), listOpenRequest)
s.NoError(err)
return len(listResp.Executions) == 0
},
waitForESToSettle,
100*time.Millisecond,
)
listClosedRequest := &workflowservice.ListClosedWorkflowExecutionsRequest{
Namespace: s.namespace,
MaximumPageSize: 100,
StartTimeFilter: &filterpb.StartTimeFilter{
EarliestTime: nil,
LatestTime: timestamppb.New(time.Now().UTC()),
},
Filters: &workflowservice.ListClosedWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{
WorkflowId: id,
}},
}
listClosedResp, err := s.client.ListClosedWorkflowExecutions(NewContext(), listClosedRequest)
s.NoError(err)
s.Equal(1, len(listClosedResp.Executions))
}
func (s *FunctionalSuite) TestSignalWithStartWorkflow_ResolveIDDeduplication() {
// setting this to 0 to be sure we are terminating the current workflow
s.OverrideDynamicConfig(dynamicconfig.WorkflowIdReuseMinimalInterval, 0)
id := "functional-signal-with-start-workflow-id-reuse-test"
wt := "functional-signal-with-start-workflow-id-reuse-test-type"
tl := "functional-signal-with-start-workflow-id-reuse-test-taskqueue"
identity := "worker1"
activityName := "activity_type1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
// Start a workflow
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we, err0 := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err0)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we.RunId))
workflowComplete := false
activityCount := int32(1)
activityCounter := int32(0)
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if activityCounter < activityCount {
activityCounter++
buf := new(bytes.Buffer)
s.Nil(binary.Write(buf, binary.LittleEndian, activityCounter))
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_SCHEDULE_ACTIVITY_TASK,
Attributes: &commandpb.Command_ScheduleActivityTaskCommandAttributes{ScheduleActivityTaskCommandAttributes: &commandpb.ScheduleActivityTaskCommandAttributes{
ActivityId: convert.Int32ToString(activityCounter),
ActivityType: &commonpb.ActivityType{Name: activityName},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: payloads.EncodeBytes(buf.Bytes()),
ScheduleToCloseTimeout: durationpb.New(100 * time.Second),
ScheduleToStartTimeout: durationpb.New(10 * time.Second),
StartToCloseTimeout: durationpb.New(50 * time.Second),
HeartbeatTimeout: durationpb.New(5 * time.Second),
}},
}}, nil
}
workflowComplete = true
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
atHandler := func(task *workflowservice.PollActivityTaskQueueResponse) (*commonpb.Payloads, bool, error) {
return payloads.EncodeString("Activity Result"), false, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
ActivityTaskHandler: atHandler,
Logger: s.Logger,
T: s.T(),
}
// Start workflows, make some progress and complete workflow
_, err := poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
s.True(workflowComplete)
// test WorkflowIdReusePolicy: RejectDuplicate
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
sRequest := &workflowservice.SignalWithStartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
SignalName: signalName,
SignalInput: signalInput,
Identity: identity,
WorkflowIdReusePolicy: enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE,
}
ctx, _ := rpc.NewContextWithTimeoutAndVersionHeaders(5 * time.Second)
resp, err := s.client.SignalWithStartWorkflowExecution(ctx, sRequest)
s.Nil(resp)
s.Error(err)
s.True(strings.Contains(err.Error(), "reject duplicate workflow Id"))
s.IsType(&serviceerror.WorkflowExecutionAlreadyStarted{}, err)
// test WorkflowIdReusePolicy: AllowDuplicateFailedOnly
sRequest.WorkflowIdReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY
ctx, _ = rpc.NewContextWithTimeoutAndVersionHeaders(5 * time.Second)
resp, err = s.client.SignalWithStartWorkflowExecution(ctx, sRequest)
s.Nil(resp)
s.Error(err)
s.True(strings.Contains(err.Error(), "allow duplicate workflow Id if last run failed"))
s.IsType(&serviceerror.WorkflowExecutionAlreadyStarted{}, err)
// test WorkflowIdReusePolicy: AllowDuplicate
sRequest.WorkflowIdReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE
ctx, _ = rpc.NewContextWithTimeoutAndVersionHeaders(5 * time.Second)
resp, err = s.client.SignalWithStartWorkflowExecution(ctx, sRequest)
s.NoError(err)
s.NotEmpty(resp.GetRunId())
s.True(resp.Started)
// Terminate workflow execution
_, err = s.client.TerminateWorkflowExecution(NewContext(), &workflowservice.TerminateWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
},
Reason: "test WorkflowIdReusePolicyAllowDuplicateFailedOnly",
Details: nil,
Identity: identity,
})
s.NoError(err)
// test WorkflowIdReusePolicy: AllowDuplicateFailedOnly
sRequest.WorkflowIdReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY
resp, err = s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
s.NotEmpty(resp.GetRunId())
s.True(resp.Started)
// test WorkflowIdReusePolicy: TerminateIfRunning (for backwards compatibility)
prevRunID := resp.RunId
sRequest.WorkflowIdReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING
resp, err = s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
s.NotEmpty(resp.GetRunId())
s.NotEqual(prevRunID, resp.GetRunId())
s.True(resp.Started)
descResp, err := s.client.DescribeWorkflowExecution(NewContext(), &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: s.namespace,
Execution: &commonpb.WorkflowExecution{WorkflowId: id, RunId: prevRunID},
})
s.NoError(err)
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, descResp.WorkflowExecutionInfo.Status)
// test WorkflowIdConflictPolicy: TerminateExisting (replaced TerminateIfRunning)
prevRunID = resp.RunId
sRequest.WorkflowIdReusePolicy = enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE
sRequest.WorkflowIdConflictPolicy = enumspb.WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING
resp, err = s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
s.NotEmpty(resp.GetRunId())
s.NotEqual(prevRunID, resp.GetRunId())
s.True(resp.Started)
descResp, err = s.client.DescribeWorkflowExecution(NewContext(), &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: s.namespace,
Execution: &commonpb.WorkflowExecution{WorkflowId: id, RunId: prevRunID},
})
s.NoError(err)
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_TERMINATED, descResp.WorkflowExecutionInfo.Status)
descResp, err = s.client.DescribeWorkflowExecution(NewContext(), &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: s.namespace,
Execution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: resp.GetRunId(),
},
})
s.NoError(err)
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING, descResp.WorkflowExecutionInfo.Status)
}
func (s *FunctionalSuite) TestSignalWithStartWorkflow_StartDelay() {
id := "functional-signal-with-start-workflow-start-delay-test"
wt := "functional-signal-with-start-workflow-start-delay-test-type"
tl := "functional-signal-with-start-workflow-start-delay-test-taskqueue"
stickyTq := "functional-signal-with-start-workflow-start-delay-test-sticky-taskqueue"
identity := "worker1"
startDelay := 3 * time.Second
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
sRequest := &workflowservice.SignalWithStartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: &commonpb.WorkflowType{Name: wt},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
SignalName: signalName,
SignalInput: signalInput,
Identity: identity,
WorkflowStartDelay: durationpb.New(startDelay),
}
reqStartTime := time.Now()
we0, startErr := s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(startErr)
var signalEvent *historypb.HistoryEvent
delayEndTime := time.Now()
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
delayEndTime = time.Now()
for _, event := range task.History.Events[task.PreviousStartedEventId:] {
if event.GetEventType() == enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED {
signalEvent = event
}
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
StickyTaskQueue: &taskqueuepb.TaskQueue{Name: stickyTq, Kind: enumspb.TASK_QUEUE_KIND_STICKY, NormalName: tl},
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: s.Logger,
T: s.T(),
}
_, pollErr := poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.NoError(pollErr)
s.GreaterOrEqual(delayEndTime.Sub(reqStartTime), startDelay)
s.NotNil(signalEvent)
s.Equal(signalName, signalEvent.GetWorkflowExecutionSignaledEventAttributes().SignalName)
s.ProtoEqual(signalInput, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Input)
s.Equal(identity, signalEvent.GetWorkflowExecutionSignaledEventAttributes().Identity)
descResp, descErr := s.client.DescribeWorkflowExecution(NewContext(), &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: s.namespace,
Execution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we0.RunId,
},
})
s.NoError(descErr)
s.Equal(enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED, descResp.WorkflowExecutionInfo.Status)
}
func (s *FunctionalSuite) TestSignalWithStartWorkflow_NoWorkflowTaskCreated() {
id := "functional-signal-with-start-workflow-no-wft-test"
wt := "functional-signal-with-start-workflow-no-wft-test-type"
tl := "functional-signal-with-start-workflow-no-wft-test-taskqueue"
identity := "worker1"
workflowType := &commonpb.WorkflowType{Name: wt}
taskQueue := &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL}
// Start workflow execution
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: workflowType,
TaskQueue: taskQueue,
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
Identity: identity,
}
we0, err := s.client.StartWorkflowExecution(NewContext(), request)
s.NoError(err)
s.Logger.Info("StartWorkflowExecution", tag.WorkflowRunID(we0.RunId))
workflowComplete := false
wtHandler := func(task *workflowservice.PollWorkflowTaskQueueResponse) ([]*commandpb.Command, error) {
if !workflowComplete {
workflowComplete = true
return []*commandpb.Command{}, nil
}
return []*commandpb.Command{{
CommandType: enumspb.COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION,
Attributes: &commandpb.Command_CompleteWorkflowExecutionCommandAttributes{CompleteWorkflowExecutionCommandAttributes: &commandpb.CompleteWorkflowExecutionCommandAttributes{
Result: payloads.EncodeString("Done"),
}},
}}, nil
}
poller := &TaskPoller{
Client: s.client,
Namespace: s.namespace,
TaskQueue: taskQueue,
StickyTaskQueue: taskQueue,
Identity: identity,
WorkflowTaskHandler: wtHandler,
Logger: s.Logger,
T: s.T(),
}
// process start task
_, err = poller.PollAndProcessWorkflowTask(WithDumpHistory)
s.NoError(err)
signalName := "my signal"
signalInput := payloads.EncodeString("my signal input")
sRequest := &workflowservice.SignalWithStartWorkflowExecutionRequest{
RequestId: uuid.New(),
Namespace: s.namespace,
WorkflowId: id,
WorkflowType: &commonpb.WorkflowType{Name: wt},
TaskQueue: &taskqueuepb.TaskQueue{Name: tl, Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
Input: nil,
WorkflowRunTimeout: durationpb.New(100 * time.Second),
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
SignalName: signalName,
SignalInput: signalInput,
SkipGenerateWorkflowTask: true,
Identity: identity,
}
we1, err := s.client.SignalWithStartWorkflowExecution(NewContext(), sRequest)
s.NoError(err)
// Send second signal which should generate a new wft
_, err = s.client.SignalWorkflowExecution(NewContext(), &workflowservice.SignalWorkflowExecutionRequest{
Namespace: s.namespace,
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we1.RunId,
},
SignalName: "signal with task",
Input: nil,
Identity: identity,
SkipGenerateWorkflowTask: false,
})
s.NoError(err)
// process signal and complete workflow
_, err = poller.PollAndProcessWorkflowTask()
s.Logger.Info("PollAndProcessWorkflowTask", tag.Error(err))
s.NoError(err)
historyEvents := s.getHistory(s.namespace, &commonpb.WorkflowExecution{
WorkflowId: id,
RunId: we1.RunId,
})
s.EqualHistoryEvents(`
1 WorkflowExecutionStarted
2 WorkflowTaskScheduled
3 WorkflowTaskStarted
4 WorkflowTaskCompleted
5 WorkflowExecutionSignaled
6 WorkflowExecutionSignaled
7 WorkflowTaskScheduled
8 WorkflowTaskStarted
9 WorkflowTaskCompleted
10 WorkflowExecutionCompleted`, historyEvents)
}