mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Adds validation in workflow validator for NexusOperation links ## Why? Covers workflow flows started by standalone operations. ## How did you test it? - [ ] built - [ ] run locally and tested manually - [ ] covered by existing tests - [x] added new unit test(s) - [ ] added new functional test(s) Closes [#10345](https://github.com/temporalio/temporal/issues/10345)
4826 lines
188 KiB
Go
4826 lines
188 KiB
Go
package frontend
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
activitypb "go.temporal.io/api/activity/v1"
|
|
batchpb "go.temporal.io/api/batch/v1"
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
failurepb "go.temporal.io/api/failure/v1"
|
|
filterpb "go.temporal.io/api/filter/v1"
|
|
historypb "go.temporal.io/api/history/v1"
|
|
namespacepb "go.temporal.io/api/namespace/v1"
|
|
replicationpb "go.temporal.io/api/replication/v1"
|
|
schedulepb "go.temporal.io/api/schedule/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
taskqueuepb "go.temporal.io/api/taskqueue/v1"
|
|
updatepb "go.temporal.io/api/update/v1"
|
|
workflowpb "go.temporal.io/api/workflow/v1"
|
|
"go.temporal.io/api/workflowservice/v1"
|
|
batchspb "go.temporal.io/server/api/batch/v1"
|
|
historyspb "go.temporal.io/server/api/history/v1"
|
|
"go.temporal.io/server/api/historyservice/v1"
|
|
"go.temporal.io/server/api/historyservicemock/v1"
|
|
"go.temporal.io/server/api/matchingservice/v1"
|
|
"go.temporal.io/server/api/matchingservicemock/v1"
|
|
persistencespb "go.temporal.io/server/api/persistence/v1"
|
|
taskqueuespb "go.temporal.io/server/api/taskqueue/v1"
|
|
"go.temporal.io/server/chasm/lib/callback"
|
|
"go.temporal.io/server/chasm/lib/nexusoperation"
|
|
"go.temporal.io/server/chasm/lib/workflow"
|
|
"go.temporal.io/server/common"
|
|
"go.temporal.io/server/common/archiver"
|
|
"go.temporal.io/server/common/archiver/provider"
|
|
"go.temporal.io/server/common/clock"
|
|
"go.temporal.io/server/common/cluster"
|
|
dc "go.temporal.io/server/common/dynamicconfig"
|
|
"go.temporal.io/server/common/headers"
|
|
"go.temporal.io/server/common/log"
|
|
"go.temporal.io/server/common/namespace"
|
|
"go.temporal.io/server/common/payload"
|
|
"go.temporal.io/server/common/payloads"
|
|
"go.temporal.io/server/common/persistence"
|
|
"go.temporal.io/server/common/persistence/visibility"
|
|
"go.temporal.io/server/common/persistence/visibility/manager"
|
|
"go.temporal.io/server/common/persistence/visibility/store/elasticsearch"
|
|
"go.temporal.io/server/common/primitives"
|
|
"go.temporal.io/server/common/primitives/timestamp"
|
|
"go.temporal.io/server/common/quotas"
|
|
"go.temporal.io/server/common/resourcetest"
|
|
"go.temporal.io/server/common/rpc/interceptor"
|
|
"go.temporal.io/server/common/searchattribute"
|
|
"go.temporal.io/server/common/searchattribute/sadefs"
|
|
"go.temporal.io/server/common/tasktoken"
|
|
"go.temporal.io/server/common/testing/protoassert"
|
|
"go.temporal.io/server/common/testing/protorequire"
|
|
"go.temporal.io/server/common/tqid"
|
|
"go.temporal.io/server/service/history/api"
|
|
"go.temporal.io/server/service/history/tests"
|
|
"go.temporal.io/server/service/worker/batcher"
|
|
"go.temporal.io/server/service/worker/scheduler"
|
|
"go.uber.org/mock/gomock"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/health"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
const (
|
|
numHistoryShards = 10
|
|
esIndexName = ""
|
|
|
|
testWorkflowID = "test-workflow-id"
|
|
testRunID = "test-run-id"
|
|
testHistoryArchivalURI = "testScheme://history/URI"
|
|
testVisibilityArchivalURI = "testScheme://visibility/URI"
|
|
)
|
|
|
|
type (
|
|
WorkflowHandlerSuite struct {
|
|
suite.Suite
|
|
protorequire.ProtoAssertions
|
|
*require.Assertions
|
|
|
|
controller *gomock.Controller
|
|
mockResource *resourcetest.Test
|
|
mockNamespaceCache *namespace.MockRegistry
|
|
mockHistoryClient *historyservicemock.MockHistoryServiceClient
|
|
mockClusterMetadata *cluster.MockMetadata
|
|
mockSearchAttributesProvider *searchattribute.MockProvider
|
|
mockSearchAttributesMapperProvider *searchattribute.MockMapperProvider
|
|
mockMatchingClient *matchingservicemock.MockMatchingServiceClient
|
|
|
|
mockProducer *persistence.MockNamespaceReplicationQueue
|
|
mockMetadataMgr *persistence.MockMetadataManager
|
|
mockExecutionManager *persistence.MockExecutionManager
|
|
mockVisibilityMgr *manager.MockVisibilityManager
|
|
mockArchivalMetadata archiver.MetadataMock
|
|
mockArchiverProvider *provider.MockArchiverProvider
|
|
mockHistoryArchiver *archiver.MockHistoryArchiver
|
|
mockVisibilityArchiver *archiver.MockVisibilityArchiver
|
|
|
|
tokenSerializer *tasktoken.Serializer
|
|
|
|
testNamespace namespace.Name
|
|
testNamespaceID namespace.ID
|
|
}
|
|
)
|
|
|
|
var testNamespaceID = primitives.MustValidateUUID("deadbeef-c001-4567-890a-bcdef0123456")
|
|
|
|
func TestWorkflowHandlerSuite(t *testing.T) {
|
|
s := new(WorkflowHandlerSuite)
|
|
suite.Run(t, s)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) SetupSuite() {
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TearDownSuite() {
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) SetupTest() {
|
|
s.Assertions = require.New(s.T())
|
|
s.ProtoAssertions = protorequire.New(s.T())
|
|
|
|
s.testNamespace = "test-namespace"
|
|
s.testNamespaceID = "e4f90ec0-1313-45be-9877-8aa41f72a45a"
|
|
|
|
s.controller = gomock.NewController(s.T())
|
|
s.mockResource = resourcetest.NewTest(s.controller, primitives.FrontendService)
|
|
s.mockNamespaceCache = s.mockResource.NamespaceCache
|
|
s.mockHistoryClient = s.mockResource.HistoryClient
|
|
s.mockClusterMetadata = s.mockResource.ClusterMetadata
|
|
s.mockSearchAttributesProvider = s.mockResource.SearchAttributesProvider
|
|
s.mockSearchAttributesMapperProvider = s.mockResource.SearchAttributesMapperProvider
|
|
s.mockMetadataMgr = s.mockResource.MetadataMgr
|
|
s.mockExecutionManager = s.mockResource.ExecutionMgr
|
|
s.mockVisibilityMgr = s.mockResource.VisibilityManager
|
|
s.mockArchivalMetadata = s.mockResource.ArchivalMetadata
|
|
s.mockArchiverProvider = s.mockResource.ArchiverProvider
|
|
s.mockMatchingClient = s.mockResource.MatchingClient
|
|
|
|
s.mockProducer = persistence.NewMockNamespaceReplicationQueue(s.controller)
|
|
s.mockHistoryArchiver = archiver.NewMockHistoryArchiver(s.controller)
|
|
s.mockVisibilityArchiver = archiver.NewMockVisibilityArchiver(s.controller)
|
|
|
|
s.tokenSerializer = tasktoken.NewSerializer()
|
|
|
|
s.mockVisibilityMgr.EXPECT().GetStoreNames().Return([]string{elasticsearch.PersistenceName}).AnyTimes()
|
|
s.mockExecutionManager.EXPECT().GetName().Return("mock-execution-manager").AnyTimes()
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TearDownTest() {
|
|
s.controller.Finish()
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) getWorkflowHandler(config *Config) *WorkflowHandler {
|
|
s.mockVisibilityMgr.EXPECT().GetIndexName().Return(esIndexName).AnyTimes()
|
|
healthInterceptor := interceptor.NewHealthInterceptor()
|
|
healthInterceptor.SetHealthy(true)
|
|
cbValidator := callback.NewValidator(
|
|
func(string) int { return 2000 },
|
|
config.CallbackURLMaxLength,
|
|
config.CallbackHeaderMaxSize,
|
|
func(string) callback.AddressMatchRules {
|
|
return callback.AddressMatchRules{
|
|
Rules: []callback.AddressMatchRule{
|
|
{Regexp: regexp.MustCompile(`.*`), AllowInsecure: true},
|
|
},
|
|
}
|
|
},
|
|
)
|
|
saValidator := searchattribute.NewValidator(
|
|
s.mockResource.GetSearchAttributesProvider(),
|
|
s.mockResource.GetSearchAttributesMapperProvider(),
|
|
config.SearchAttributesNumberOfKeysLimit,
|
|
config.SearchAttributesSizeOfValueLimit,
|
|
config.SearchAttributesTotalSizeLimit,
|
|
s.mockResource.GetVisibilityManager(),
|
|
visibility.AllowListForValidation(
|
|
s.mockResource.GetVisibilityManager().GetStoreNames(),
|
|
config.VisibilityAllowList,
|
|
),
|
|
config.SuppressErrorSetSystemSearchAttribute,
|
|
)
|
|
return NewWorkflowHandler(
|
|
cbValidator,
|
|
config,
|
|
s.mockProducer,
|
|
s.mockResource.GetVisibilityManager(),
|
|
s.mockResource.GetLogger(),
|
|
s.mockResource.GetThrottledLogger(),
|
|
s.mockResource.GetExecutionManager().GetName(),
|
|
s.mockResource.GetClusterMetadataManager(),
|
|
s.mockResource.GetMetadataManager(),
|
|
s.mockResource.GetHistoryClient(),
|
|
s.mockResource.GetMatchingClient(),
|
|
nil,
|
|
nil, // Not initializing the scheduler client here.
|
|
s.mockResource.GetArchiverProvider(),
|
|
s.mockResource.GetPayloadSerializer(),
|
|
s.mockResource.GetNamespaceRegistry(),
|
|
s.mockResource.GetSearchAttributesMapperProvider(),
|
|
s.mockResource.GetSearchAttributesProvider(),
|
|
saValidator,
|
|
s.mockResource.GetClusterMetadata(),
|
|
s.mockResource.GetArchivalMetadata(),
|
|
health.NewServer(),
|
|
clock.NewRealTimeSource(),
|
|
s.mockResource.GetMembershipMonitor(),
|
|
healthInterceptor,
|
|
scheduler.NewSpecBuilder(),
|
|
true,
|
|
nil, // Not testing activity handler here
|
|
nexusoperation.NewFrontendHandler(
|
|
nil,
|
|
nil,
|
|
s.mockResource.GetLogger(),
|
|
s.mockResource.GetNamespaceRegistry(),
|
|
nil,
|
|
s.mockResource.GetSearchAttributesMapperProvider(),
|
|
nil,
|
|
),
|
|
nil, // Not testing CHASM registry here
|
|
quotas.NoopRequestRateLimiter,
|
|
workflow.NewValidator(
|
|
workflow.NewConfig(dc.NewNoopCollection()),
|
|
s.mockSearchAttributesMapperProvider,
|
|
saValidator,
|
|
),
|
|
)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDisableListVisibilityByFilter() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
config := s.newConfig()
|
|
config.DisableListVisibilityByFilter = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().GetReadStoreName(testNamespace).Return("").AnyTimes()
|
|
|
|
// test list open by wid
|
|
listRequest := &workflowservice.ListOpenWorkflowExecutionsRequest{
|
|
Namespace: testNamespace.String(),
|
|
StartTimeFilter: &filterpb.StartTimeFilter{
|
|
EarliestTime: nil,
|
|
LatestTime: timestamppb.New(time.Now().UTC()),
|
|
},
|
|
Filters: &workflowservice.ListOpenWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{
|
|
WorkflowId: "wid",
|
|
}},
|
|
}
|
|
_, err := wh.ListOpenWorkflowExecutions(context.Background(), listRequest)
|
|
s.Error(err)
|
|
s.Equal(errListNotAllowed, err)
|
|
|
|
// test list open by workflow type
|
|
listRequest.Filters = &workflowservice.ListOpenWorkflowExecutionsRequest_TypeFilter{TypeFilter: &filterpb.WorkflowTypeFilter{
|
|
Name: "workflow-type",
|
|
}}
|
|
_, err = wh.ListOpenWorkflowExecutions(context.Background(), listRequest)
|
|
s.Error(err)
|
|
s.Equal(errListNotAllowed, err)
|
|
|
|
// test list close by wid
|
|
listRequest2 := &workflowservice.ListClosedWorkflowExecutionsRequest{
|
|
Namespace: testNamespace.String(),
|
|
StartTimeFilter: &filterpb.StartTimeFilter{
|
|
EarliestTime: nil,
|
|
LatestTime: timestamppb.New(time.Now().UTC()),
|
|
},
|
|
Filters: &workflowservice.ListClosedWorkflowExecutionsRequest_ExecutionFilter{ExecutionFilter: &filterpb.WorkflowExecutionFilter{
|
|
WorkflowId: "wid",
|
|
}},
|
|
}
|
|
_, err = wh.ListClosedWorkflowExecutions(context.Background(), listRequest2)
|
|
s.Error(err)
|
|
s.Equal(errListNotAllowed, err)
|
|
|
|
// test list close by workflow type
|
|
listRequest2.Filters = &workflowservice.ListClosedWorkflowExecutionsRequest_TypeFilter{TypeFilter: &filterpb.WorkflowTypeFilter{
|
|
Name: "workflow-type",
|
|
}}
|
|
_, err = wh.ListClosedWorkflowExecutions(context.Background(), listRequest2)
|
|
s.Error(err)
|
|
s.Equal(errListNotAllowed, err)
|
|
|
|
// test list close by workflow status
|
|
failedStatus := enumspb.WORKFLOW_EXECUTION_STATUS_FAILED
|
|
listRequest2.Filters = &workflowservice.ListClosedWorkflowExecutionsRequest_StatusFilter{StatusFilter: &filterpb.StatusFilter{Status: failedStatus}}
|
|
_, err = wh.ListClosedWorkflowExecutions(context.Background(), listRequest2)
|
|
s.Error(err)
|
|
s.Equal(errListNotAllowed, err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestPollForTask_Failed_ContextTimeoutTooShort() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
bgCtx := context.Background()
|
|
_, err := wh.PollWorkflowTaskQueue(bgCtx, &workflowservice.PollWorkflowTaskQueueRequest{})
|
|
s.Error(err)
|
|
s.Equal(common.ErrContextTimeoutNotSet, err)
|
|
|
|
_, err = wh.PollActivityTaskQueue(bgCtx, &workflowservice.PollActivityTaskQueueRequest{})
|
|
s.Error(err)
|
|
s.Equal(common.ErrContextTimeoutNotSet, err)
|
|
|
|
shortCtx, cancel := context.WithTimeout(bgCtx, common.MinLongPollTimeout-time.Millisecond)
|
|
defer cancel()
|
|
|
|
_, err = wh.PollWorkflowTaskQueue(shortCtx, &workflowservice.PollWorkflowTaskQueueRequest{})
|
|
s.Error(err)
|
|
s.Equal(common.ErrContextTimeoutTooShort, err)
|
|
|
|
_, err = wh.PollActivityTaskQueue(shortCtx, &workflowservice.PollActivityTaskQueueRequest{})
|
|
s.Error(err)
|
|
s.Equal(common.ErrContextTimeoutTooShort, err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_StartRequestNotSet() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
_, err := wh.StartWorkflowExecution(context.Background(), nil)
|
|
s.Error(err)
|
|
s.Equal(errRequestNotSet, err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_NamespaceNotSet() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.EmptyName).Return(namespace.EmptyID, serviceerror.NewNamespaceNotFound("missing-namespace")).AnyTimes()
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(namespace.EmptyName).Return(nil, nil)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
// Namespace: "forget to specify",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.Error(err)
|
|
var notFound *serviceerror.NamespaceNotFound
|
|
s.ErrorAs(err, ¬Found)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_WorkflowIdNotSet() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.Error(err)
|
|
s.Equal(workflow.ErrWorkflowIDNotSet, err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_WorkflowTypeNotSet() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowTaskTimeout: durationpb.New(1 * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.Error(err)
|
|
s.Equal(errWorkflowTypeNotSet, err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_TaskQueueNotSet() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "",
|
|
},
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.Error(err)
|
|
s.Equal(serviceerror.NewInvalidArgument("missing task queue name"), err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidExecutionTimeout() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "An invalid WorkflowExecutionTimeoutSeconds is set on request")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidRunTimeout() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "An invalid WorkflowRunTimeoutSeconds is set on request")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_EnsureNonNilRetryPolicyInitialized() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.Error(err)
|
|
s.Equal(&commonpb.RetryPolicy{
|
|
BackoffCoefficient: 2.0,
|
|
InitialInterval: durationpb.New(time.Second),
|
|
MaximumInterval: durationpb.New(100 * time.Second),
|
|
}, startWorkflowExecutionRequest.RetryPolicy)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_EnsureNilRetryPolicyNotInitialized() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.Error(err)
|
|
s.Nil(startWorkflowExecutionRequest.RetryPolicy)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidTaskTimeout() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowTaskTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "An invalid WorkflowTaskTimeoutSeconds is set on request")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_CronAndStartDelaySet() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowTaskTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
CronSchedule: "dummy-cron-schedule",
|
|
WorkflowStartDelay: durationpb.New(10 * time.Second),
|
|
}
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
s.ErrorIs(err, workflow.ErrCronAndStartDelaySet)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidStartDelay() {
|
|
config := s.newConfig()
|
|
config.RPS = dc.GetIntPropertyFn(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
startWorkflowExecutionRequest := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
WorkflowExecutionTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowRunTimeout: durationpb.New(1 * time.Second),
|
|
WorkflowTaskTimeout: durationpb.New(time.Duration(-1) * time.Second),
|
|
RetryPolicy: &commonpb.RetryPolicy{
|
|
InitialInterval: durationpb.New(1 * time.Second),
|
|
BackoffCoefficient: 2,
|
|
MaximumInterval: durationpb.New(2 * time.Second),
|
|
MaximumAttempts: 1,
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
WorkflowStartDelay: durationpb.New(-10 * time.Second),
|
|
}
|
|
|
|
_, err := wh.StartWorkflowExecution(context.Background(), startWorkflowExecutionRequest)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, workflow.ErrInvalidWorkflowStartDelaySeconds.Error())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_InvalidWorkflowIdReusePolicy_TerminateIfRunning() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
WorkflowId: testWorkflowID,
|
|
WorkflowType: &commonpb.WorkflowType{Name: "WORKFLOW"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "TASK_QUEUE", Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
|
WorkflowIdReusePolicy: enumspb.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING,
|
|
WorkflowIdConflictPolicy: enumspb.WORKFLOW_ID_CONFLICT_POLICY_FAIL,
|
|
}
|
|
|
|
resp, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
|
|
s.Nil(resp)
|
|
s.Equal(err, serviceerror.NewInvalidArgument(
|
|
"Invalid WorkflowIDReusePolicy: WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING cannot be used together with a WorkflowIDConflictPolicy"))
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_InvalidWorkflowIdReusePolicy_RejectDuplicate() {
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
WorkflowId: testWorkflowID,
|
|
WorkflowType: &commonpb.WorkflowType{Name: "WORKFLOW"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "TASK_QUEUE", Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
|
WorkflowIdReusePolicy: enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE,
|
|
WorkflowIdConflictPolicy: enumspb.WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING,
|
|
}
|
|
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
resp, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
s.Nil(resp)
|
|
s.Equal(err, serviceerror.NewInvalidArgument(
|
|
"Invalid WorkflowIDReusePolicy: WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE cannot be used together with WorkflowIdConflictPolicy WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING"))
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_DefaultWorkflowIdDuplicationPolicies() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespace.NewID(), nil)
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), mock.MatchedBy(
|
|
func(request *historyservice.StartWorkflowExecutionRequest) bool {
|
|
return request.StartRequest.WorkflowIdReusePolicy == enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE &&
|
|
request.StartRequest.WorkflowIdConflictPolicy == enumspb.WORKFLOW_ID_CONFLICT_POLICY_FAIL
|
|
},
|
|
)).Return(&historyservice.StartWorkflowExecutionResponse{Started: true}, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
WorkflowId: testWorkflowID,
|
|
WorkflowType: &commonpb.WorkflowType{Name: "WORKFLOW"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "TASK_QUEUE", Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
|
// both policies are not specified
|
|
}
|
|
|
|
resp, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
s.NoError(err)
|
|
s.True(resp.Started)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
config.MaxLinksPerRequest = dc.GetIntPropertyFnFilteredByNamespace(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
}
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: strings.Repeat("X", 4000),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "link exceeds allowed size of 4000")
|
|
|
|
req.Links = []*commonpb.Link{}
|
|
for range 11 {
|
|
req.Links = append(req.Links, &commonpb.Link{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: "dont-care",
|
|
RunId: uuid.NewString(),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "cannot attach more than 10 links per request, got 11")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "workflow event link must not have an empty namespace field")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "present",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "workflow event link must not have an empty workflow ID field")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "present",
|
|
WorkflowId: "present",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "workflow event link must not have an empty run ID field")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "present",
|
|
WorkflowId: "present",
|
|
RunId: uuid.NewString(),
|
|
Reference: &commonpb.Link_WorkflowEvent_EventRef{
|
|
EventRef: &commonpb.Link_WorkflowEvent_EventReference{
|
|
EventId: 3,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "workflow event link ref cannot have an unspecified event type and a non-zero event ID")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_BatchJob_{
|
|
BatchJob: &commonpb.Link_BatchJob{},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "batch job link must not have an empty job ID")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_NexusOperation_{
|
|
NexusOperation: &commonpb.Link_NexusOperation{},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "nexus operation link must not have an empty namespace field")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_NexusOperation_{
|
|
NexusOperation: &commonpb.Link_NexusOperation{
|
|
Namespace: "present",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "nexus operation link must not have an empty operation ID field")
|
|
|
|
req.Links = []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_NexusOperation_{
|
|
NexusOperation: &commonpb.Link_NexusOperation{
|
|
Namespace: "present",
|
|
OperationId: "present",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err = wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "nexus operation link must not have an empty run ID field")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidCallbackLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
CompletionCallbacks: []*commonpb.Callback{
|
|
{
|
|
Variant: &commonpb.Callback_Internal_{
|
|
Internal: &commonpb.Callback_Internal{},
|
|
},
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
_, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "workflow event link must not have an empty namespace field")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Failed_InvalidAggregatedLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
config.MaxLinksPerRequest = dc.GetIntPropertyFnFilteredByNamespace(10)
|
|
config.CallbackEndpointConfigs = dc.GetTypedPropertyFnFilteredByNamespace(callback.AddressMatchRules{
|
|
Rules: []callback.AddressMatchRule{
|
|
{
|
|
Regexp: regexp.MustCompile(`.*`),
|
|
AllowInsecure: true,
|
|
},
|
|
},
|
|
})
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
CompletionCallbacks: []*commonpb.Callback{
|
|
{
|
|
Variant: &commonpb.Callback_Nexus_{
|
|
Nexus: &commonpb.Callback_Nexus{
|
|
Url: "http://localhost/test",
|
|
},
|
|
},
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{},
|
|
},
|
|
},
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: "dont-care",
|
|
RunId: "run-id-0",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// add 10 links and one of them is duplicated in the callback
|
|
req.Links = []*commonpb.Link{}
|
|
for i := range 10 {
|
|
req.Links = append(req.Links, &commonpb.Link{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: "dont-care",
|
|
RunId: fmt.Sprintf("run-id-%d", i),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
_, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "cannot attach more than 10 links per request, got 11")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_Priority() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil).AnyTimes()
|
|
|
|
request := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
Priority: &commonpb.Priority{PriorityKey: -1},
|
|
}
|
|
|
|
_, err := wh.StartWorkflowExecution(context.Background(), request)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "priority key can't be negative")
|
|
// NOTE: only testing a single validation scenario here; the priority validation has its own unit tests
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_InvalidWorkflowIdConflictPolicy() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
WorkflowId: testWorkflowID,
|
|
WorkflowType: &commonpb.WorkflowType{Name: "WORKFLOW"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "TASK_QUEUE", Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
|
SignalName: "SIGNAL",
|
|
WorkflowIdConflictPolicy: enumspb.WORKFLOW_ID_CONFLICT_POLICY_FAIL,
|
|
}
|
|
|
|
resp, err := wh.SignalWithStartWorkflowExecution(context.Background(), req)
|
|
|
|
s.Nil(resp)
|
|
s.Equal(err, serviceerror.NewInvalidArgument(
|
|
"Invalid WorkflowIDConflictPolicy: WORKFLOW_ID_CONFLICT_POLICY_FAIL is not supported for this operation."))
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_InvalidWorkflowIdReusePolicy_TerminateIfRunning() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
WorkflowId: testWorkflowID,
|
|
WorkflowType: &commonpb.WorkflowType{Name: "WORKFLOW"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "TASK_QUEUE", Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
|
SignalName: "SIGNAL",
|
|
WorkflowIdReusePolicy: enumspb.WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING,
|
|
WorkflowIdConflictPolicy: enumspb.WORKFLOW_ID_CONFLICT_POLICY_FAIL,
|
|
}
|
|
|
|
resp, err := wh.SignalWithStartWorkflowExecution(context.Background(), req)
|
|
|
|
s.Nil(resp)
|
|
s.Equal(err, serviceerror.NewInvalidArgument(
|
|
"Invalid WorkflowIDReusePolicy: WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING cannot be used together with a WorkflowIDConflictPolicy"))
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_DefaultWorkflowIdDuplicationPolicies() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespace.NewID(), nil)
|
|
s.mockHistoryClient.EXPECT().SignalWithStartWorkflowExecution(gomock.Any(), mock.MatchedBy(
|
|
func(request *historyservice.SignalWithStartWorkflowExecutionRequest) bool {
|
|
return request.SignalWithStartRequest.WorkflowIdReusePolicy == enumspb.WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE &&
|
|
request.SignalWithStartRequest.WorkflowIdConflictPolicy == enumspb.WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING
|
|
},
|
|
)).Return(&historyservice.SignalWithStartWorkflowExecutionResponse{Started: true}, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
WorkflowId: testWorkflowID,
|
|
WorkflowType: &commonpb.WorkflowType{Name: "WORKFLOW"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "TASK_QUEUE", Kind: enumspb.TASK_QUEUE_KIND_NORMAL},
|
|
SignalName: "SIGNAL",
|
|
// both policies are not specified
|
|
}
|
|
|
|
resp, err := wh.SignalWithStartWorkflowExecution(context.Background(), req)
|
|
s.NoError(err)
|
|
s.True(resp.Started)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_Failed_InvalidLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
config.MaxLinksPerRequest = dc.GetIntPropertyFnFilteredByNamespace(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
SignalName: "dont-care",
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
RequestId: uuid.NewString(),
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: strings.Repeat("X", 4000),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := wh.SignalWithStartWorkflowExecution(context.Background(), req)
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "link exceeds allowed size of 4000")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_Priority() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil).AnyTimes()
|
|
|
|
request := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "workflow-id",
|
|
WorkflowType: &commonpb.WorkflowType{
|
|
Name: "workflow-type",
|
|
},
|
|
TaskQueue: &taskqueuepb.TaskQueue{
|
|
Name: "task-queue",
|
|
},
|
|
SignalName: "signal-name",
|
|
Priority: &commonpb.Priority{PriorityKey: -1},
|
|
}
|
|
|
|
_, err := wh.SignalWithStartWorkflowExecution(context.Background(), request)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "priority key can't be negative")
|
|
// NOTE: only testing a single validation scenario here; the priority validation has its own unit tests
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestSignalWorkflowExecution_Failed_InvalidLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
config.MaxLinksPerRequest = dc.GetIntPropertyFnFilteredByNamespace(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.SignalWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
},
|
|
SignalName: "dont-care",
|
|
Identity: "test",
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: strings.Repeat("X", 4000),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := wh.SignalWorkflowExecution(context.Background(), req)
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "link exceeds allowed size of 4000")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestTerminateWorkflowExecution_Failed_InvalidLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
config.MaxLinksPerRequest = dc.GetIntPropertyFnFilteredByNamespace(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.TerminateWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
},
|
|
Reason: "dont-care",
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: strings.Repeat("X", 4000),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := wh.TerminateWorkflowExecution(context.Background(), req)
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "link exceeds allowed size of 4000")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestTerminateWorkflowExecution_Succeed_WithDefaultReasonAndIdentity() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(testNamespace).Return(namespaceID, nil)
|
|
s.mockHistoryClient.EXPECT().TerminateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.TerminateWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.TerminateWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal("workflow-id", request.TerminateRequest.WorkflowExecution.GetWorkflowId())
|
|
// Verify that default values are set when reason and identity are empty
|
|
s.Equal(defaultUserTerminateReason, request.TerminateRequest.GetReason())
|
|
s.Equal(defaultUserTerminateIdentity, request.TerminateRequest.GetIdentity())
|
|
return &historyservice.TerminateWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
|
|
req := &workflowservice.TerminateWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
},
|
|
}
|
|
|
|
_, err := wh.TerminateWorkflowExecution(context.Background(), req)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestTerminateWorkflowExecution_Succeed_WithCustomReasonAndIdentity() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(testNamespace).Return(namespaceID, nil)
|
|
reason := "reason"
|
|
identity := "identity"
|
|
s.mockHistoryClient.EXPECT().TerminateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.TerminateWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.TerminateWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal("workflow-id", request.TerminateRequest.WorkflowExecution.GetWorkflowId())
|
|
// Verify that custom values are preserved and not overwritten by defaults
|
|
s.Equal(reason, request.TerminateRequest.GetReason())
|
|
s.Equal(identity, request.TerminateRequest.GetIdentity())
|
|
return &historyservice.TerminateWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
|
|
req := &workflowservice.TerminateWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
},
|
|
Reason: reason,
|
|
Identity: identity,
|
|
}
|
|
|
|
_, err := wh.TerminateWorkflowExecution(context.Background(), req)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestRequestCancelWorkflowExecution_Failed_InvalidLinks() {
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).AnyTimes().Return(nil, nil)
|
|
config := s.newConfig()
|
|
config.MaxLinksPerRequest = dc.GetIntPropertyFnFilteredByNamespace(10)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
req := &workflowservice.RequestCancelWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
},
|
|
Reason: "dont-care",
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "dont-care",
|
|
WorkflowId: strings.Repeat("X", 4000),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := wh.RequestCancelWorkflowExecution(context.Background(), req)
|
|
var invalidArgument *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArgument)
|
|
s.ErrorContains(err, "link exceeds allowed size of 4000")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestRegisterNamespace_Failure_InvalidArchivalURI() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "random URI"))
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(errors.New("invalid URI"))
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testHistoryArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testVisibilityArchivalURI,
|
|
)
|
|
_, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestRegisterNamespace_Success_EnabledWithNoArchivalURI() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", testHistoryArchivalURI))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", testVisibilityArchivalURI))
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(enumspb.ARCHIVAL_STATE_ENABLED, "", enumspb.ARCHIVAL_STATE_ENABLED, "")
|
|
_, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestRegisterNamespace_Success_EnabledWithArchivalURI() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "invalidURI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "invalidURI"))
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testHistoryArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testVisibilityArchivalURI,
|
|
)
|
|
_, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestRegisterNamespace_Success_ClusterNotConfiguredForArchival() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewDisabledArchvialConfig())
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewDisabledArchvialConfig())
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testVisibilityArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
"invalidURI",
|
|
)
|
|
_, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestRegisterNamespace_Success_NotEnabled() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(enumspb.ARCHIVAL_STATE_UNSPECIFIED, "", enumspb.ARCHIVAL_STATE_UNSPECIFIED, "")
|
|
_, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDeprecateNamespace_Success() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{}, nil)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(&persistence.GetNamespaceResponse{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: &persistencespb.NamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_REGISTERED,
|
|
},
|
|
Config: &persistencespb.NamespaceConfig{},
|
|
ReplicationConfig: &persistencespb.NamespaceReplicationConfig{
|
|
ActiveClusterName: cluster.TestCurrentClusterName,
|
|
Clusters: []string{cluster.TestCurrentClusterName},
|
|
},
|
|
},
|
|
}, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(_ context.Context, request *persistence.UpdateNamespaceRequest) error {
|
|
s.Equal(enumspb.NAMESPACE_STATE_DEPRECATED, request.Namespace.Info.State)
|
|
return nil
|
|
},
|
|
)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(enumspb.ARCHIVAL_STATE_UNSPECIFIED, "", enumspb.ARCHIVAL_STATE_UNSPECIFIED, "")
|
|
|
|
resp, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
respDeprecate, errDeprecate := wh.UpdateNamespace(context.Background(), &workflowservice.UpdateNamespaceRequest{
|
|
Namespace: req.Namespace,
|
|
UpdateInfo: &namespacepb.UpdateNamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_DEPRECATED,
|
|
},
|
|
})
|
|
s.NoError(errDeprecate)
|
|
s.NotNil(respDeprecate)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDeprecateNamespace_Error() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{}, nil)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(&persistence.GetNamespaceResponse{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: &persistencespb.NamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_REGISTERED,
|
|
},
|
|
Config: &persistencespb.NamespaceConfig{},
|
|
ReplicationConfig: &persistencespb.NamespaceReplicationConfig{
|
|
ActiveClusterName: cluster.TestCurrentClusterName,
|
|
Clusters: []string{cluster.TestCurrentClusterName},
|
|
},
|
|
},
|
|
}, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(_ context.Context, request *persistence.UpdateNamespaceRequest) error {
|
|
s.Equal(enumspb.NAMESPACE_STATE_DEPRECATED, request.Namespace.Info.State)
|
|
return serviceerror.NewInternal("db is down")
|
|
},
|
|
)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(enumspb.ARCHIVAL_STATE_UNSPECIFIED, "", enumspb.ARCHIVAL_STATE_UNSPECIFIED, "")
|
|
|
|
resp, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
respDeprecate, errDeprecate := wh.UpdateNamespace(context.Background(), &workflowservice.UpdateNamespaceRequest{
|
|
Namespace: req.Namespace,
|
|
UpdateInfo: &namespacepb.UpdateNamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_DEPRECATED,
|
|
},
|
|
})
|
|
s.Error(errDeprecate)
|
|
s.Nil(respDeprecate)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDeleteNamespace_Success() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{}, nil)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(&persistence.GetNamespaceResponse{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: &persistencespb.NamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_REGISTERED,
|
|
},
|
|
Config: &persistencespb.NamespaceConfig{},
|
|
ReplicationConfig: &persistencespb.NamespaceReplicationConfig{
|
|
ActiveClusterName: cluster.TestCurrentClusterName,
|
|
Clusters: []string{cluster.TestCurrentClusterName},
|
|
},
|
|
},
|
|
}, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(_ context.Context, request *persistence.UpdateNamespaceRequest) error {
|
|
s.Equal(enumspb.NAMESPACE_STATE_DELETED, request.Namespace.Info.State)
|
|
return nil
|
|
},
|
|
)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(enumspb.ARCHIVAL_STATE_UNSPECIFIED, "", enumspb.ARCHIVAL_STATE_UNSPECIFIED, "")
|
|
|
|
resp, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
respDelete, errDelete := wh.UpdateNamespace(context.Background(), &workflowservice.UpdateNamespaceRequest{
|
|
Namespace: req.Namespace,
|
|
UpdateInfo: &namespacepb.UpdateNamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_DELETED,
|
|
},
|
|
})
|
|
s.NoError(errDelete)
|
|
s.NotNil(respDelete)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDeleteNamespace_Error() {
|
|
s.mockClusterMetadata.EXPECT().IsGlobalNamespaceEnabled().Return(false)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI")).Times(2)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNamespaceNotFound("missing-namespace"))
|
|
s.mockMetadataMgr.EXPECT().CreateNamespace(gomock.Any(), gomock.Any()).Return(&persistence.CreateNamespaceResponse{
|
|
ID: testNamespaceID,
|
|
}, nil)
|
|
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{}, nil)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(&persistence.GetNamespaceResponse{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: &persistencespb.NamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_REGISTERED,
|
|
},
|
|
Config: &persistencespb.NamespaceConfig{},
|
|
ReplicationConfig: &persistencespb.NamespaceReplicationConfig{
|
|
ActiveClusterName: cluster.TestCurrentClusterName,
|
|
Clusters: []string{cluster.TestCurrentClusterName},
|
|
},
|
|
},
|
|
}, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(_ context.Context, request *persistence.UpdateNamespaceRequest) error {
|
|
s.Equal(enumspb.NAMESPACE_STATE_DELETED, request.Namespace.Info.State)
|
|
return serviceerror.NewInternal("db is down")
|
|
},
|
|
)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := registerNamespaceRequest(enumspb.ARCHIVAL_STATE_UNSPECIFIED, "", enumspb.ARCHIVAL_STATE_UNSPECIFIED, "")
|
|
|
|
resp, err := wh.RegisterNamespace(context.Background(), req)
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
respDelete, errDelete := wh.UpdateNamespace(context.Background(), &workflowservice.UpdateNamespaceRequest{
|
|
Namespace: req.Namespace,
|
|
UpdateInfo: &namespacepb.UpdateNamespaceInfo{
|
|
State: enumspb.NAMESPACE_STATE_DELETED,
|
|
},
|
|
})
|
|
s.Error(errDelete)
|
|
s.Nil(respDelete)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_Success_ArchivalDisabled() {
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_DISABLED, URI: ""},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_DISABLED, URI: ""},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := &workflowservice.DescribeNamespaceRequest{
|
|
Namespace: "test-namespace",
|
|
}
|
|
result, err := wh.DescribeNamespace(context.Background(), req)
|
|
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_DISABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal("", result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_DISABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal("", result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_Success_ArchivalEnabled() {
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testHistoryArchivalURI},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testVisibilityArchivalURI},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
req := &workflowservice.DescribeNamespaceRequest{
|
|
Namespace: "test-namespace",
|
|
}
|
|
result, err := wh.DescribeNamespace(context.Background(), req)
|
|
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal(testHistoryArchivalURI, result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal(testVisibilityArchivalURI, result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_WeakConsistency_ByName() {
|
|
ns := namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Id: testNamespaceID, Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{Retention: timestamp.DurationFromDays(1)},
|
|
cluster.TestCurrentClusterName,
|
|
)
|
|
s.mockNamespaceCache.EXPECT().
|
|
GetNamespaceWithOptions(namespace.Name("test-namespace"), namespace.GetNamespaceOptions{DisableReadthrough: true}).
|
|
Return(ns, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
result, err := wh.DescribeNamespace(context.Background(), &workflowservice.DescribeNamespaceRequest{
|
|
Namespace: "test-namespace",
|
|
WeakConsistency: true,
|
|
})
|
|
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.Equal("test-namespace", result.NamespaceInfo.Name)
|
|
s.Equal(testNamespaceID, result.NamespaceInfo.Id)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_WeakConsistency_ByID() {
|
|
ns := namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Id: testNamespaceID, Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{Retention: timestamp.DurationFromDays(1)},
|
|
cluster.TestCurrentClusterName,
|
|
)
|
|
s.mockNamespaceCache.EXPECT().
|
|
GetNamespaceByIDWithOptions(namespace.ID(testNamespaceID), namespace.GetNamespaceOptions{DisableReadthrough: true}).
|
|
Return(ns, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
result, err := wh.DescribeNamespace(context.Background(), &workflowservice.DescribeNamespaceRequest{
|
|
Id: testNamespaceID,
|
|
WeakConsistency: true,
|
|
})
|
|
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.Equal(testNamespaceID, result.NamespaceInfo.Id)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_WeakConsistency_CacheMiss() {
|
|
notFound := serviceerror.NewNamespaceNotFound("missing")
|
|
s.mockNamespaceCache.EXPECT().
|
|
GetNamespaceWithOptions(namespace.Name("missing"), namespace.GetNamespaceOptions{DisableReadthrough: true}).
|
|
Return(nil, notFound)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
result, err := wh.DescribeNamespace(context.Background(), &workflowservice.DescribeNamespaceRequest{
|
|
Namespace: "missing",
|
|
WeakConsistency: true,
|
|
})
|
|
|
|
s.Error(err)
|
|
s.Nil(result)
|
|
var nsNotFound *serviceerror.NamespaceNotFound
|
|
s.ErrorAs(err, &nsNotFound)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_WeakConsistency_NameAndIDNotSet() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
result, err := wh.DescribeNamespace(context.Background(), &workflowservice.DescribeNamespaceRequest{
|
|
WeakConsistency: true,
|
|
})
|
|
|
|
s.Equal(errNamespaceNotSet, err)
|
|
s.Nil(result)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeNamespace_WeakConsistency_BothNameAndIDSet() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
result, err := wh.DescribeNamespace(context.Background(), &workflowservice.DescribeNamespaceRequest{
|
|
Namespace: "test-namespace",
|
|
Id: testNamespaceID,
|
|
WeakConsistency: true,
|
|
})
|
|
|
|
s.Error(err)
|
|
s.Nil(result)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Failure_UpdateExistingArchivalURI() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testHistoryArchivalURI},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testVisibilityArchivalURI},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest(
|
|
"",
|
|
enumspb.ARCHIVAL_STATE_UNSPECIFIED,
|
|
"updated visibility URI",
|
|
enumspb.ARCHIVAL_STATE_UNSPECIFIED,
|
|
)
|
|
_, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Failure_InvalidArchivalURI() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_DISABLED, URI: ""},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_DISABLED, URI: ""},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(errors.New("invalid URI"))
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest(
|
|
"testScheme://invalid/updated/history/URI",
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
"",
|
|
enumspb.ARCHIVAL_STATE_UNSPECIFIED,
|
|
)
|
|
_, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Success_ArchivalEnabledToArchivalDisabledWithoutSettingURI() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testHistoryArchivalURI},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testVisibilityArchivalURI},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest(
|
|
"",
|
|
enumspb.ARCHIVAL_STATE_DISABLED,
|
|
"",
|
|
enumspb.ARCHIVAL_STATE_DISABLED,
|
|
)
|
|
result, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_DISABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal(testHistoryArchivalURI, result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_DISABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal(testVisibilityArchivalURI, result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Success_ClusterNotConfiguredForArchival() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: "some random history URI"},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: "some random visibility URI"},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewDisabledArchvialConfig())
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewDisabledArchvialConfig())
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest("", enumspb.ARCHIVAL_STATE_DISABLED, "", enumspb.ARCHIVAL_STATE_UNSPECIFIED)
|
|
result, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal("some random history URI", result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal("some random visibility URI", result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Success_ArchivalEnabledToArchivalDisabledWithSettingBucket() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testHistoryArchivalURI},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testVisibilityArchivalURI},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest(
|
|
testHistoryArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_DISABLED,
|
|
testVisibilityArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_DISABLED,
|
|
)
|
|
result, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_DISABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal(testHistoryArchivalURI, result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_DISABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal(testVisibilityArchivalURI, result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Success_ArchivalEnabledToEnabled() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testHistoryArchivalURI},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_ENABLED, URI: testVisibilityArchivalURI},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest(
|
|
testHistoryArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testVisibilityArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
)
|
|
result, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal(testHistoryArchivalURI, result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal(testVisibilityArchivalURI, result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateNamespace_Success_ArchivalNeverEnabledToEnabled() {
|
|
s.mockMetadataMgr.EXPECT().GetMetadata(gomock.Any()).Return(&persistence.GetMetadataResponse{
|
|
NotificationVersion: int64(0),
|
|
}, nil)
|
|
getNamespaceResp := persistenceGetNamespaceResponse(
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_DISABLED, URI: ""},
|
|
&namespace.ArchivalConfigState{State: enumspb.ARCHIVAL_STATE_DISABLED, URI: ""},
|
|
)
|
|
s.mockMetadataMgr.EXPECT().GetNamespace(gomock.Any(), gomock.Any()).Return(getNamespaceResp, nil)
|
|
s.mockMetadataMgr.EXPECT().UpdateNamespace(gomock.Any(), gomock.Any()).Return(nil)
|
|
s.mockClusterMetadata.EXPECT().GetAllClusterInfo().Return(cluster.TestAllClusterInfo).AnyTimes()
|
|
s.mockClusterMetadata.EXPECT().GetCurrentClusterName().Return(cluster.TestCurrentClusterName).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetHistoryConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "some random URI"))
|
|
s.mockHistoryArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockVisibilityArchiver.EXPECT().ValidateURI(gomock.Any()).Return(nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
updateReq := updateRequest(
|
|
testHistoryArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
testVisibilityArchivalURI,
|
|
enumspb.ARCHIVAL_STATE_ENABLED,
|
|
)
|
|
result, err := wh.UpdateNamespace(context.Background(), updateReq)
|
|
s.NoError(err)
|
|
s.NotNil(result)
|
|
s.NotNil(result.Config)
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetHistoryArchivalState())
|
|
s.Equal(testHistoryArchivalURI, result.Config.GetHistoryArchivalUri())
|
|
s.Equal(enumspb.ARCHIVAL_STATE_ENABLED, result.Config.GetVisibilityArchivalState())
|
|
s.Equal(testVisibilityArchivalURI, result.Config.GetVisibilityArchivalUri())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestHistoryArchived() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
getHistoryRequest := &workflowservice.GetWorkflowExecutionHistoryRequest{}
|
|
s.False(wh.historyArchived(context.Background(), getHistoryRequest, "test-namespace"))
|
|
|
|
getHistoryRequest = &workflowservice.GetWorkflowExecutionHistoryRequest{
|
|
Execution: &commonpb.WorkflowExecution{},
|
|
}
|
|
s.False(wh.historyArchived(context.Background(), getHistoryRequest, "test-namespace"))
|
|
|
|
s.mockHistoryClient.EXPECT().GetMutableState(gomock.Any(), gomock.Any()).Return(nil, nil)
|
|
getHistoryRequest = &workflowservice.GetWorkflowExecutionHistoryRequest{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
RunId: testRunID,
|
|
},
|
|
}
|
|
s.False(wh.historyArchived(context.Background(), getHistoryRequest, "test-namespace"))
|
|
|
|
s.mockHistoryClient.EXPECT().GetMutableState(gomock.Any(), gomock.Any()).Return(nil, serviceerror.NewNotFound("got archival indication error"))
|
|
getHistoryRequest = &workflowservice.GetWorkflowExecutionHistoryRequest{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
RunId: testRunID,
|
|
},
|
|
}
|
|
s.True(wh.historyArchived(context.Background(), getHistoryRequest, "test-namespace"))
|
|
|
|
s.mockHistoryClient.EXPECT().GetMutableState(gomock.Any(), gomock.Any()).Return(nil, errors.New("got non-archival indication error"))
|
|
getHistoryRequest = &workflowservice.GetWorkflowExecutionHistoryRequest{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
RunId: testRunID,
|
|
},
|
|
}
|
|
s.False(wh.historyArchived(context.Background(), getHistoryRequest, "test-namespace"))
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestGetArchivedHistory_Failure_NamespaceCacheEntryError() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceByID(gomock.Any()).Return(nil, errors.New("error getting namespace"))
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.getArchivedHistory(context.Background(), getHistoryRequest(nil), s.testNamespaceID)
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestGetArchivedHistory_Failure_ArchivalURIEmpty() {
|
|
namespaceEntry := namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{
|
|
HistoryArchivalState: enumspb.ARCHIVAL_STATE_DISABLED,
|
|
HistoryArchivalUri: "",
|
|
VisibilityArchivalState: enumspb.ARCHIVAL_STATE_DISABLED,
|
|
VisibilityArchivalUri: "",
|
|
},
|
|
"")
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceByID(gomock.Any()).Return(namespaceEntry, nil).AnyTimes()
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.getArchivedHistory(context.Background(), getHistoryRequest(nil), s.testNamespaceID)
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestGetArchivedHistory_Failure_InvalidURI() {
|
|
namespaceEntry := namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{
|
|
HistoryArchivalState: enumspb.ARCHIVAL_STATE_ENABLED,
|
|
HistoryArchivalUri: "uri without scheme",
|
|
VisibilityArchivalState: enumspb.ARCHIVAL_STATE_DISABLED,
|
|
VisibilityArchivalUri: "",
|
|
},
|
|
"")
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceByID(gomock.Any()).Return(namespaceEntry, nil).AnyTimes()
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.getArchivedHistory(context.Background(), getHistoryRequest(nil), s.testNamespaceID)
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestGetArchivedHistory_Success_GetFirstPage() {
|
|
namespaceEntry := namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{
|
|
HistoryArchivalState: enumspb.ARCHIVAL_STATE_ENABLED,
|
|
HistoryArchivalUri: testHistoryArchivalURI,
|
|
VisibilityArchivalState: enumspb.ARCHIVAL_STATE_DISABLED,
|
|
VisibilityArchivalUri: "",
|
|
},
|
|
"")
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceByID(gomock.Any()).Return(namespaceEntry, nil).AnyTimes()
|
|
|
|
nextPageToken := []byte{'1', '2', '3'}
|
|
historyBatch1 := &historypb.History{
|
|
Events: []*historypb.HistoryEvent{
|
|
{EventId: 1},
|
|
{EventId: 2},
|
|
},
|
|
}
|
|
historyBatch2 := &historypb.History{
|
|
Events: []*historypb.HistoryEvent{
|
|
{EventId: 3},
|
|
{EventId: 4},
|
|
{EventId: 5},
|
|
},
|
|
}
|
|
history := &historypb.History{}
|
|
history.Events = append(history.Events, historyBatch1.Events...)
|
|
history.Events = append(history.Events, historyBatch2.Events...)
|
|
s.mockHistoryArchiver.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).Return(&archiver.GetHistoryResponse{
|
|
NextPageToken: nextPageToken,
|
|
HistoryBatches: []*historypb.History{historyBatch1, historyBatch2},
|
|
}, nil)
|
|
s.mockArchiverProvider.EXPECT().GetHistoryArchiver(gomock.Any()).Return(s.mockHistoryArchiver, nil)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.getArchivedHistory(context.Background(), getHistoryRequest(nil), s.testNamespaceID)
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
s.NotNil(resp.History)
|
|
s.Equal(history, resp.History)
|
|
s.Equal(nextPageToken, resp.NextPageToken)
|
|
s.True(resp.GetArchived())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListArchivedVisibility_Failure_InvalidRequest() {
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewDisabledArchvialConfig())
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.ListArchivedWorkflowExecutions(context.Background(), &workflowservice.ListArchivedWorkflowExecutionsRequest{})
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListArchivedVisibility_Failure_ClusterNotConfiguredForArchival() {
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewDisabledArchvialConfig())
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.ListArchivedWorkflowExecutions(context.Background(), listArchivedWorkflowExecutionsTestRequest())
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListArchivedVisibility_Failure_NamespaceCacheEntryError() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespace(gomock.Any()).Return(nil, errors.New("error getting namespace"))
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "random URI")).Times(2)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.ListArchivedWorkflowExecutions(context.Background(), listArchivedWorkflowExecutionsTestRequest())
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListArchivedVisibility_Failure_NamespaceNotConfiguredForArchival() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespace(gomock.Any()).Return(namespace.NewLocalNamespaceForTest(
|
|
nil,
|
|
&persistencespb.NamespaceConfig{
|
|
VisibilityArchivalState: enumspb.ARCHIVAL_STATE_DISABLED,
|
|
},
|
|
"",
|
|
), nil)
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "random URI")).Times(2)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.ListArchivedWorkflowExecutions(context.Background(), listArchivedWorkflowExecutionsTestRequest())
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListArchivedVisibility_Failure_InvalidURI() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespace(gomock.Any()).Return(namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{
|
|
VisibilityArchivalState: enumspb.ARCHIVAL_STATE_DISABLED,
|
|
VisibilityArchivalUri: "uri without scheme",
|
|
},
|
|
"",
|
|
), nil)
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "random URI")).Times(2)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.ListArchivedWorkflowExecutions(context.Background(), listArchivedWorkflowExecutionsTestRequest())
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListArchivedVisibility_Success() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespace(gomock.Any()).Return(namespace.NewLocalNamespaceForTest(
|
|
&persistencespb.NamespaceInfo{Name: "test-namespace"},
|
|
&persistencespb.NamespaceConfig{
|
|
VisibilityArchivalState: enumspb.ARCHIVAL_STATE_ENABLED,
|
|
VisibilityArchivalUri: testVisibilityArchivalURI,
|
|
},
|
|
"",
|
|
), nil).AnyTimes()
|
|
s.mockArchivalMetadata.EXPECT().GetVisibilityConfig().Return(archiver.NewArchivalConfig("enabled", dc.GetStringPropertyFn("enabled"), dc.GetBoolPropertyFn(true), "disabled", "random URI")).Times(2)
|
|
s.mockVisibilityArchiver.EXPECT().Query(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&archiver.QueryVisibilityResponse{}, nil)
|
|
s.mockArchiverProvider.EXPECT().GetVisibilityArchiver(gomock.Any()).Return(s.mockVisibilityArchiver, nil)
|
|
s.mockSearchAttributesProvider.EXPECT().GetSearchAttributes("", false)
|
|
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.ListArchivedWorkflowExecutions(context.Background(), listArchivedWorkflowExecutionsTestRequest())
|
|
s.NotNil(resp)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestGetSearchAttributes() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
ctx := context.Background()
|
|
s.mockResource.SearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), false).Return(searchattribute.TestNameTypeMap(), nil)
|
|
resp, err := wh.GetSearchAttributes(ctx, &workflowservice.GetSearchAttributesRequest{})
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeWorkflowExecution_RunningStatus() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
now := timestamppb.New(time.Now())
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(
|
|
s.testNamespaceID,
|
|
nil,
|
|
).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().DescribeWorkflowExecution(gomock.Any(), gomock.Any()).Return(
|
|
&historyservice.DescribeWorkflowExecutionResponse{
|
|
WorkflowExecutionInfo: &workflowpb.WorkflowExecutionInfo{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
RunId: testRunID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
|
|
ExecutionTime: now,
|
|
Memo: nil,
|
|
SearchAttributes: nil,
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
request := &workflowservice.DescribeWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
},
|
|
}
|
|
_, err := wh.DescribeWorkflowExecution(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeWorkflowExecution_CompletedStatus() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
now := timestamppb.New(time.Now())
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(
|
|
s.testNamespaceID,
|
|
nil,
|
|
).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().DescribeWorkflowExecution(gomock.Any(), gomock.Any()).Return(
|
|
&historyservice.DescribeWorkflowExecutionResponse{
|
|
WorkflowExecutionInfo: &workflowpb.WorkflowExecutionInfo{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
RunId: testRunID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED,
|
|
ExecutionTime: now,
|
|
Memo: nil,
|
|
SearchAttributes: nil,
|
|
},
|
|
},
|
|
nil,
|
|
)
|
|
|
|
request := &workflowservice.DescribeWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
},
|
|
}
|
|
_, err := wh.DescribeWorkflowExecution(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListWorkflowExecutions() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(s.testNamespace).Return(s.testNamespaceID, nil).AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().GetReadStoreName(s.testNamespace).Return(elasticsearch.PersistenceName).AnyTimes()
|
|
|
|
query := "WorkflowId = 'wid'"
|
|
listRequest := &workflowservice.ListWorkflowExecutionsRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
PageSize: int32(config.VisibilityMaxPageSize(s.testNamespace.String())),
|
|
Query: query,
|
|
}
|
|
ctx := context.Background()
|
|
|
|
// page size <= 0 => max page size = 1000
|
|
s.mockVisibilityMgr.EXPECT().ListWorkflowExecutions(
|
|
gomock.Any(),
|
|
&manager.ListWorkflowExecutionsRequestV2{
|
|
NamespaceID: s.testNamespaceID,
|
|
Namespace: s.testNamespace,
|
|
PageSize: config.VisibilityMaxPageSize(s.testNamespace.String()),
|
|
NextPageToken: nil,
|
|
Query: query,
|
|
},
|
|
).Return(&manager.ListWorkflowExecutionsResponse{}, nil)
|
|
_, err := wh.ListWorkflowExecutions(ctx, listRequest)
|
|
s.NoError(err)
|
|
s.Equal(query, listRequest.GetQuery())
|
|
|
|
// page size > 1000 => max page size = 1000
|
|
s.mockVisibilityMgr.EXPECT().ListWorkflowExecutions(
|
|
gomock.Any(),
|
|
&manager.ListWorkflowExecutionsRequestV2{
|
|
NamespaceID: s.testNamespaceID,
|
|
Namespace: s.testNamespace,
|
|
PageSize: config.VisibilityMaxPageSize(s.testNamespace.String()),
|
|
NextPageToken: nil,
|
|
Query: query,
|
|
},
|
|
).Return(&manager.ListWorkflowExecutionsResponse{}, nil)
|
|
listRequest.PageSize = int32(config.VisibilityMaxPageSize(s.testNamespace.String())) + 1
|
|
_, err = wh.ListWorkflowExecutions(ctx, listRequest)
|
|
s.NoError(err)
|
|
s.Equal(query, listRequest.GetQuery())
|
|
|
|
// page size between 0 and 1000
|
|
s.mockVisibilityMgr.EXPECT().ListWorkflowExecutions(
|
|
gomock.Any(),
|
|
&manager.ListWorkflowExecutionsRequestV2{
|
|
NamespaceID: s.testNamespaceID,
|
|
Namespace: s.testNamespace,
|
|
PageSize: 10,
|
|
NextPageToken: nil,
|
|
Query: query,
|
|
},
|
|
).Return(&manager.ListWorkflowExecutionsResponse{}, nil)
|
|
listRequest.PageSize = 10
|
|
_, err = wh.ListWorkflowExecutions(ctx, listRequest)
|
|
s.NoError(err)
|
|
s.Equal(query, listRequest.GetQuery())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestCountWorkflowExecutions() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(s.testNamespaceID, nil).AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 5}, nil)
|
|
|
|
countRequest := &workflowservice.CountWorkflowExecutionsRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
}
|
|
ctx := context.Background()
|
|
|
|
query := "WorkflowId = 'wid'"
|
|
countRequest.Query = query
|
|
resp, err := wh.CountWorkflowExecutions(ctx, countRequest)
|
|
s.NoError(err)
|
|
s.Equal(int64(5), resp.Count)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestVerifyHistoryIsComplete() {
|
|
logger := log.NewTestLogger()
|
|
events := make([]*historyspb.StrippedHistoryEvent, 50)
|
|
for i := range events {
|
|
events[i] = &historyspb.StrippedHistoryEvent{EventId: int64(i + 1)}
|
|
}
|
|
var eventsWithHoles []*historyspb.StrippedHistoryEvent
|
|
eventsWithHoles = append(eventsWithHoles, events[9:12]...)
|
|
eventsWithHoles = append(eventsWithHoles, events[20:31]...)
|
|
|
|
testCases := []struct {
|
|
events []*historyspb.StrippedHistoryEvent
|
|
firstEventID int64
|
|
lastEventID int64
|
|
isFirstPage bool
|
|
isLastPage bool
|
|
pageSize int
|
|
isResultErr bool
|
|
}{
|
|
{events[:1], 1, 1, true, true, 1000, false},
|
|
{events[:5], 1, 5, true, true, 1000, false},
|
|
{events[9:31], 10, 31, true, true, 1000, false},
|
|
{events[9:29], 10, 50, true, false, 20, false},
|
|
{events[9:30], 10, 50, true, false, 20, false},
|
|
|
|
{events[9:29], 1, 50, false, false, 20, false},
|
|
{events[9:29], 1, 29, false, true, 20, false},
|
|
|
|
{eventsWithHoles, 1, 50, false, false, 22, true},
|
|
{eventsWithHoles, 10, 50, true, false, 22, true},
|
|
{eventsWithHoles, 1, 31, false, true, 22, true},
|
|
{eventsWithHoles, 10, 31, true, true, 1000, true},
|
|
|
|
{events[9:31], 9, 31, true, true, 1000, true},
|
|
{events[9:31], 9, 50, true, false, 22, true},
|
|
{events[9:31], 11, 31, true, true, 1000, true},
|
|
{events[9:31], 11, 50, true, false, 22, true},
|
|
|
|
{events[9:31], 10, 30, true, true, 1000, true},
|
|
{events[9:31], 1, 30, false, true, 22, true},
|
|
{events[9:31], 10, 32, true, true, 1000, true},
|
|
{events[9:31], 1, 32, false, true, 22, true},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
err := api.VerifyHistoryIsComplete(
|
|
logger,
|
|
tc.events[0],
|
|
tc.events[len(tc.events)-1],
|
|
len(tc.events),
|
|
tc.firstEventID,
|
|
tc.lastEventID,
|
|
tc.isFirstPage,
|
|
tc.isLastPage,
|
|
tc.pageSize,
|
|
)
|
|
if tc.isResultErr {
|
|
s.Error(err, "testcase %v failed", i)
|
|
} else {
|
|
s.NoError(err, "testcase %v failed", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestGetSystemInfo() {
|
|
wh := s.getWorkflowHandler(s.newConfig())
|
|
|
|
resp, err := wh.GetSystemInfo(context.Background(), &workflowservice.GetSystemInfoRequest{})
|
|
s.NoError(err)
|
|
s.Equal(headers.ServerVersion, resp.ServerVersion)
|
|
s.True(resp.Capabilities.SignalAndQueryHeader)
|
|
s.True(resp.Capabilities.InternalErrorDifferentiation)
|
|
s.True(resp.Capabilities.ActivityFailureIncludeHeartbeat)
|
|
s.True(resp.Capabilities.SupportsSchedules)
|
|
s.True(resp.Capabilities.EncodedFailureAttributes)
|
|
s.True(resp.Capabilities.UpsertMemo)
|
|
s.True(resp.Capabilities.Nexus)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_Terminate() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
inputString := "unit test"
|
|
visibilityQuery := "WorkflowType='unit-test'"
|
|
jobId := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
params := &batchspb.BatchOperationInput{
|
|
NamespaceId: namespaceID.String(),
|
|
BatchType: enumspb.BATCH_OPERATION_TYPE_TERMINATE,
|
|
Request: &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
VisibilityQuery: visibilityQuery,
|
|
JobId: jobId,
|
|
Reason: inputString,
|
|
Operation: &workflowservice.StartBatchOperationRequest_TerminationOperation{
|
|
TerminationOperation: &batchpb.BatchOperationTermination{
|
|
Identity: inputString,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
inputPayload, err := payloads.Encode(params)
|
|
s.NoError(err)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(batcher.BatchWFTypeProtobufName, request.StartRequest.WorkflowType.Name)
|
|
s.Equal(primitives.PerNSWorkerTaskQueue, request.StartRequest.TaskQueue.Name)
|
|
s.Equal(enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, request.StartRequest.WorkflowIdReusePolicy)
|
|
s.Equal(inputString, request.StartRequest.Identity)
|
|
s.ProtoEqual(payload.EncodeString(batcher.BatchTypeTerminate), request.StartRequest.Memo.Fields[batcher.BatchOperationTypeMemo])
|
|
s.ProtoEqual(payload.EncodeString(inputString), request.StartRequest.Memo.Fields[batcher.BatchReasonMemo])
|
|
s.ProtoEqual(payload.EncodeString(inputString), request.StartRequest.SearchAttributes.IndexedFields[sadefs.BatcherUser])
|
|
s.ProtoEqual(inputPayload, request.StartRequest.Input)
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobId,
|
|
Reason: inputString,
|
|
Operation: &workflowservice.StartBatchOperationRequest_TerminationOperation{
|
|
TerminationOperation: &batchpb.BatchOperationTermination{
|
|
Identity: inputString,
|
|
},
|
|
},
|
|
VisibilityQuery: visibilityQuery,
|
|
}
|
|
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_Cancellation() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
inputString := "unit test"
|
|
visibilityQuery := "WorkflowType='unit-test'"
|
|
jobId := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
params := &batchspb.BatchOperationInput{
|
|
NamespaceId: namespaceID.String(),
|
|
BatchType: enumspb.BATCH_OPERATION_TYPE_CANCEL,
|
|
Request: &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
VisibilityQuery: visibilityQuery,
|
|
JobId: jobId,
|
|
Reason: inputString,
|
|
Operation: &workflowservice.StartBatchOperationRequest_CancellationOperation{
|
|
CancellationOperation: &batchpb.BatchOperationCancellation{
|
|
Identity: inputString,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
inputPayload, err := payloads.Encode(params)
|
|
s.NoError(err)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(batcher.BatchWFTypeProtobufName, request.StartRequest.WorkflowType.Name)
|
|
s.Equal(primitives.PerNSWorkerTaskQueue, request.StartRequest.TaskQueue.Name)
|
|
s.Equal(enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, request.StartRequest.WorkflowIdReusePolicy)
|
|
s.Equal(inputString, request.StartRequest.Identity)
|
|
s.ProtoEqual(payload.EncodeString(batcher.BatchTypeCancel), request.StartRequest.Memo.Fields[batcher.BatchOperationTypeMemo])
|
|
s.ProtoEqual(payload.EncodeString(inputString), request.StartRequest.Memo.Fields[batcher.BatchReasonMemo])
|
|
s.ProtoEqual(payload.EncodeString(inputString), request.StartRequest.SearchAttributes.IndexedFields[sadefs.BatcherUser])
|
|
s.ProtoEqual(inputPayload, request.StartRequest.Input)
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobId,
|
|
Reason: inputString,
|
|
Operation: &workflowservice.StartBatchOperationRequest_CancellationOperation{
|
|
CancellationOperation: &batchpb.BatchOperationCancellation{
|
|
Identity: inputString,
|
|
},
|
|
},
|
|
VisibilityQuery: visibilityQuery,
|
|
}
|
|
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_Signal() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
inputString := "unit test"
|
|
visibilityQuery := "WorkflowType='unit-test'"
|
|
signalName := "signal name"
|
|
jobId := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
signalPayloads := payloads.EncodeString(signalName)
|
|
params := &batchspb.BatchOperationInput{
|
|
NamespaceId: namespaceID.String(),
|
|
BatchType: enumspb.BATCH_OPERATION_TYPE_SIGNAL,
|
|
Request: &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
VisibilityQuery: visibilityQuery,
|
|
JobId: jobId,
|
|
Reason: inputString,
|
|
Operation: &workflowservice.StartBatchOperationRequest_SignalOperation{
|
|
SignalOperation: &batchpb.BatchOperationSignal{
|
|
Signal: signalName,
|
|
Input: signalPayloads,
|
|
Identity: inputString,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
inputPayload, err := payloads.Encode(params)
|
|
s.NoError(err)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(batcher.BatchWFTypeProtobufName, request.StartRequest.WorkflowType.Name)
|
|
s.Equal(primitives.PerNSWorkerTaskQueue, request.StartRequest.TaskQueue.Name)
|
|
s.Equal(enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, request.StartRequest.WorkflowIdReusePolicy)
|
|
s.Equal(inputString, request.StartRequest.Identity)
|
|
s.ProtoEqual(payload.EncodeString(batcher.BatchTypeSignal), request.StartRequest.Memo.Fields[batcher.BatchOperationTypeMemo])
|
|
s.ProtoEqual(payload.EncodeString(inputString), request.StartRequest.Memo.Fields[batcher.BatchReasonMemo])
|
|
s.ProtoEqual(payload.EncodeString(inputString), request.StartRequest.SearchAttributes.IndexedFields[sadefs.BatcherUser])
|
|
s.ProtoEqual(inputPayload, request.StartRequest.Input)
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobId,
|
|
Operation: &workflowservice.StartBatchOperationRequest_SignalOperation{
|
|
SignalOperation: &batchpb.BatchOperationSignal{
|
|
Signal: signalName,
|
|
Input: signalPayloads,
|
|
Identity: inputString,
|
|
},
|
|
},
|
|
Reason: inputString,
|
|
VisibilityQuery: visibilityQuery,
|
|
}
|
|
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_WorkflowExecutions_Signal() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
executions := []*commonpb.WorkflowExecution{
|
|
{
|
|
WorkflowId: uuid.NewString(),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
}
|
|
reason := "reason"
|
|
identity := "identity"
|
|
signalName := "signal name"
|
|
jobId := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
signalPayloads := payloads.EncodeString(signalName)
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobId,
|
|
Reason: reason,
|
|
Executions: executions,
|
|
Operation: &workflowservice.StartBatchOperationRequest_SignalOperation{
|
|
SignalOperation: &batchpb.BatchOperationSignal{
|
|
Signal: signalName,
|
|
Input: signalPayloads,
|
|
Identity: identity,
|
|
},
|
|
},
|
|
}
|
|
params := &batchspb.BatchOperationInput{
|
|
NamespaceId: namespaceID.String(),
|
|
BatchType: enumspb.BATCH_OPERATION_TYPE_SIGNAL,
|
|
Request: request,
|
|
}
|
|
inputPayload, err := payloads.Encode(params)
|
|
s.NoError(err)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(batcher.BatchWFTypeProtobufName, request.StartRequest.WorkflowType.Name)
|
|
s.Equal(primitives.PerNSWorkerTaskQueue, request.StartRequest.TaskQueue.Name)
|
|
s.Equal(enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, request.StartRequest.WorkflowIdReusePolicy)
|
|
s.Equal(identity, request.StartRequest.Identity)
|
|
s.ProtoEqual(payload.EncodeString(batcher.BatchTypeSignal), request.StartRequest.Memo.Fields[batcher.BatchOperationTypeMemo])
|
|
s.ProtoEqual(payload.EncodeString(reason), request.StartRequest.Memo.Fields[batcher.BatchReasonMemo])
|
|
s.ProtoEqual(payload.EncodeString(identity), request.StartRequest.SearchAttributes.IndexedFields[sadefs.BatcherUser])
|
|
s.ProtoEqual(inputPayload, request.StartRequest.Input)
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_WorkflowExecutions_Reset() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
executions := []*commonpb.WorkflowExecution{
|
|
{
|
|
WorkflowId: uuid.NewString(),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
}
|
|
reason := "reason"
|
|
identity := "identity"
|
|
jobId := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
params := &batchspb.BatchOperationInput{
|
|
NamespaceId: namespaceID.String(),
|
|
BatchType: enumspb.BATCH_OPERATION_TYPE_RESET,
|
|
Request: &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobId,
|
|
Reason: reason,
|
|
Executions: executions,
|
|
Operation: &workflowservice.StartBatchOperationRequest_ResetOperation{
|
|
ResetOperation: &batchpb.BatchOperationReset{
|
|
Identity: identity,
|
|
ResetType: enumspb.RESET_TYPE_LAST_WORKFLOW_TASK,
|
|
ResetReapplyType: enumspb.RESET_REAPPLY_TYPE_NONE,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
inputPayload, err := payloads.Encode(params)
|
|
s.NoError(err)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(batcher.BatchWFTypeProtobufName, request.StartRequest.WorkflowType.Name)
|
|
s.Equal(primitives.PerNSWorkerTaskQueue, request.StartRequest.TaskQueue.Name)
|
|
s.Equal(enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, request.StartRequest.WorkflowIdReusePolicy)
|
|
s.Equal(identity, request.StartRequest.Identity)
|
|
s.ProtoEqual(payload.EncodeString(batcher.BatchTypeReset), request.StartRequest.Memo.Fields[batcher.BatchOperationTypeMemo])
|
|
s.ProtoEqual(payload.EncodeString(reason), request.StartRequest.Memo.Fields[batcher.BatchReasonMemo])
|
|
s.ProtoEqual(payload.EncodeString(identity), request.StartRequest.SearchAttributes.IndexedFields[sadefs.BatcherUser])
|
|
s.ProtoEqual(inputPayload, request.StartRequest.Input)
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobId,
|
|
Operation: &workflowservice.StartBatchOperationRequest_ResetOperation{
|
|
ResetOperation: &batchpb.BatchOperationReset{
|
|
ResetType: enumspb.RESET_TYPE_LAST_WORKFLOW_TASK,
|
|
ResetReapplyType: enumspb.RESET_REAPPLY_TYPE_NONE,
|
|
Identity: identity,
|
|
},
|
|
},
|
|
Reason: reason,
|
|
Executions: executions,
|
|
}
|
|
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_WorkflowExecutions_Reset_WithPostResetOperations() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
executions := []*commonpb.WorkflowExecution{
|
|
{
|
|
WorkflowId: uuid.NewString(),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
}
|
|
reason := "reason"
|
|
identity := "identity"
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
// Create post-reset operations to test the serialization fix
|
|
postResetOps := []*workflowpb.PostResetOperation{
|
|
{
|
|
Variant: &workflowpb.PostResetOperation_UpdateWorkflowOptions_{
|
|
UpdateWorkflowOptions: &workflowpb.PostResetOperation_UpdateWorkflowOptions{
|
|
WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"versioning_override"}},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(batcher.BatchWFTypeProtobufName, request.StartRequest.WorkflowType.Name)
|
|
s.Equal(primitives.PerNSWorkerTaskQueue, request.StartRequest.TaskQueue.Name)
|
|
s.Equal(enumspb.WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, request.StartRequest.WorkflowIdReusePolicy)
|
|
s.Equal(identity, request.StartRequest.Identity)
|
|
s.ProtoEqual(payload.EncodeString(batcher.BatchTypeReset), request.StartRequest.Memo.Fields[batcher.BatchOperationTypeMemo])
|
|
s.ProtoEqual(payload.EncodeString(reason), request.StartRequest.Memo.Fields[batcher.BatchReasonMemo])
|
|
s.ProtoEqual(payload.EncodeString(identity), request.StartRequest.SearchAttributes.IndexedFields[sadefs.BatcherUser])
|
|
|
|
// Decode the input and verify PostResetOperations are correctly set
|
|
var batchParams batchspb.BatchOperationInput
|
|
err := payloads.Decode(request.StartRequest.Input, &batchParams)
|
|
s.NoError(err)
|
|
|
|
// Verify that PostResetOperations slice has the correct length and no nil values
|
|
s.Len(batchParams.Request.Operation.(*workflowservice.StartBatchOperationRequest_ResetOperation).ResetOperation.PostResetOperations, len(postResetOps))
|
|
|
|
for i, encoded := range batchParams.Request.Operation.(*workflowservice.StartBatchOperationRequest_ResetOperation).ResetOperation.PostResetOperations {
|
|
s.ProtoEqual(postResetOps[i], encoded)
|
|
}
|
|
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: uuid.NewString(),
|
|
Operation: &workflowservice.StartBatchOperationRequest_ResetOperation{
|
|
ResetOperation: &batchpb.BatchOperationReset{
|
|
Options: &commonpb.ResetOptions{
|
|
Target: &commonpb.ResetOptions_WorkflowTaskId{
|
|
WorkflowTaskId: 10,
|
|
},
|
|
},
|
|
PostResetOperations: postResetOps,
|
|
Identity: identity,
|
|
},
|
|
},
|
|
Reason: reason,
|
|
Executions: executions,
|
|
}
|
|
|
|
_, err := wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_WorkflowExecutions_Reset_EmptyPostResetOperations() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
executions := []*commonpb.WorkflowExecution{
|
|
{
|
|
WorkflowId: uuid.NewString(),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
}
|
|
reason := "reason"
|
|
identity := "identity"
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.StartWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.StartWorkflowExecutionResponse, error) {
|
|
// Decode the input and verify PostResetOperations slice is properly initialized
|
|
var batchParams batchspb.BatchOperationInput
|
|
err := payloads.Decode(request.StartRequest.Input, &batchParams)
|
|
s.NoError(err)
|
|
s.Len(batchParams.Request.Operation.(*workflowservice.StartBatchOperationRequest_ResetOperation).ResetOperation.PostResetOperations, 0)
|
|
|
|
return &historyservice.StartWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: uuid.NewString(),
|
|
Operation: &workflowservice.StartBatchOperationRequest_ResetOperation{
|
|
ResetOperation: &batchpb.BatchOperationReset{
|
|
Options: &commonpb.ResetOptions{
|
|
Target: &commonpb.ResetOptions_WorkflowTaskId{
|
|
WorkflowTaskId: 10,
|
|
},
|
|
},
|
|
PostResetOperations: []*workflowpb.PostResetOperation{}, // Empty slice
|
|
Identity: identity,
|
|
},
|
|
},
|
|
Reason: reason,
|
|
Executions: executions,
|
|
}
|
|
|
|
_, err := wh.StartBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_WorkflowExecutions_TooMany() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
executions := []*commonpb.WorkflowExecution{
|
|
{
|
|
WorkflowId: uuid.NewString(),
|
|
RunId: uuid.NewString(),
|
|
},
|
|
}
|
|
reason := "reason"
|
|
identity := "identity"
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
// StartBatchOperation API uses CountWorkflowExecutions to know how many existing in-flight batch operations.
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 1}, nil)
|
|
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: uuid.NewString(),
|
|
Operation: &workflowservice.StartBatchOperationRequest_CancellationOperation{
|
|
CancellationOperation: &batchpb.BatchOperationCancellation{
|
|
Identity: identity,
|
|
},
|
|
},
|
|
Reason: reason,
|
|
Executions: executions,
|
|
}
|
|
|
|
_, err := wh.StartBatchOperation(context.Background(), request)
|
|
s.EqualError(err, "Max concurrent batch operations is reached")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_InvalidRequest() {
|
|
request := &workflowservice.StartBatchOperationRequest{
|
|
Namespace: "",
|
|
JobId: uuid.NewString(),
|
|
Operation: &workflowservice.StartBatchOperationRequest_SignalOperation{
|
|
SignalOperation: &batchpb.BatchOperationSignal{
|
|
Signal: "signalName",
|
|
Identity: "identity",
|
|
},
|
|
},
|
|
Reason: uuid.NewString(),
|
|
VisibilityQuery: uuid.NewString(),
|
|
}
|
|
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
var invalidArgumentErr *serviceerror.InvalidArgument
|
|
_, err := wh.StartBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.Namespace = uuid.NewString()
|
|
request.JobId = ""
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.JobId = uuid.NewString()
|
|
request.Operation = nil
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.Operation = &workflowservice.StartBatchOperationRequest_SignalOperation{
|
|
SignalOperation: &batchpb.BatchOperationSignal{
|
|
Signal: "signalName",
|
|
Identity: "identity",
|
|
},
|
|
}
|
|
request.Reason = ""
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.Reason = uuid.NewString()
|
|
request.VisibilityQuery = ""
|
|
_, err = wh.StartBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStopBatchOperation() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
jobID := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().TerminateWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.TerminateWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.TerminateWorkflowExecutionResponse, error) {
|
|
s.Equal(namespaceID.String(), request.NamespaceId)
|
|
s.Equal(jobID, request.TerminateRequest.WorkflowExecution.GetWorkflowId())
|
|
s.Equal("", request.TerminateRequest.WorkflowExecution.GetRunId())
|
|
return &historyservice.TerminateWorkflowExecutionResponse{}, nil
|
|
},
|
|
)
|
|
request := &workflowservice.StopBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobID,
|
|
Reason: "reason",
|
|
}
|
|
|
|
_, err := wh.StopBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestStopBatchOperation_InvalidRequest() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
request := &workflowservice.StopBatchOperationRequest{
|
|
Namespace: "",
|
|
JobId: uuid.NewString(),
|
|
Reason: "reason",
|
|
}
|
|
|
|
var invalidArgumentErr *serviceerror.InvalidArgument
|
|
_, err := wh.StopBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.Namespace = uuid.NewString()
|
|
request.JobId = ""
|
|
_, err = wh.StopBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.JobId = uuid.NewString()
|
|
request.Reason = ""
|
|
_, err = wh.StopBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeBatchOperation_CompletedStatus() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
jobID := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
now := timestamppb.New(time.Now())
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.Run("StatsNotInMemo", func() {
|
|
s.mockHistoryClient.EXPECT().DescribeWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.DescribeWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.DescribeWorkflowExecutionResponse, error) {
|
|
return &historyservice.DescribeWorkflowExecutionResponse{
|
|
WorkflowExecutionInfo: &workflowpb.WorkflowExecutionInfo{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: jobID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED,
|
|
ExecutionTime: now,
|
|
Memo: &commonpb.Memo{
|
|
Fields: map[string]*commonpb.Payload{
|
|
batcher.BatchOperationTypeMemo: payload.EncodeString(batcher.BatchTypeReset),
|
|
},
|
|
},
|
|
SearchAttributes: nil,
|
|
},
|
|
}, nil
|
|
},
|
|
)
|
|
request := &workflowservice.DescribeBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobID,
|
|
}
|
|
|
|
_, err := wh.DescribeBatchOperation(context.Background(), request)
|
|
s.Require().Error(err)
|
|
s.Contains(err.Error(), "batch operation stats are not present in the memo")
|
|
})
|
|
s.Run("StatsInMemo", func() {
|
|
s.mockHistoryClient.EXPECT().DescribeWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.DescribeWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.DescribeWorkflowExecutionResponse, error) {
|
|
|
|
statsPayload, err := payload.Encode(batcher.BatchOperationStats{
|
|
NumSuccess: 2,
|
|
NumFailure: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
return &historyservice.DescribeWorkflowExecutionResponse{
|
|
WorkflowExecutionInfo: &workflowpb.WorkflowExecutionInfo{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: jobID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_COMPLETED,
|
|
ExecutionTime: now,
|
|
Memo: &commonpb.Memo{
|
|
Fields: map[string]*commonpb.Payload{
|
|
batcher.BatchOperationTypeMemo: payload.EncodeString(batcher.BatchTypeTerminate),
|
|
batcher.BatchOperationStatsMemo: statsPayload,
|
|
},
|
|
},
|
|
SearchAttributes: nil,
|
|
},
|
|
}, nil
|
|
},
|
|
)
|
|
request := &workflowservice.DescribeBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobID,
|
|
}
|
|
|
|
resp, err := wh.DescribeBatchOperation(context.Background(), request)
|
|
s.Require().NoError(err)
|
|
s.Equal(jobID, resp.GetJobId())
|
|
s.Equal(now, resp.GetStartTime())
|
|
s.Equal(now, resp.GetCloseTime())
|
|
s.Equal(enumspb.BATCH_OPERATION_TYPE_TERMINATE, resp.GetOperationType())
|
|
s.Equal(enumspb.BATCH_OPERATION_STATE_COMPLETED, resp.GetState())
|
|
s.Equal(int64(3), resp.GetTotalOperationCount())
|
|
s.Equal(int64(2), resp.GetCompleteOperationCount())
|
|
s.Equal(int64(1), resp.GetFailureOperationCount())
|
|
})
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeBatchOperation_RunningStatus() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
jobID := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
now := timestamppb.New(time.Now())
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().DescribeWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.DescribeWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.DescribeWorkflowExecutionResponse, error) {
|
|
hbdPayload, err := payloads.Encode(batcher.HeartBeatDetails{
|
|
TotalEstimate: 5,
|
|
SuccessCount: 3,
|
|
ErrorCount: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
return &historyservice.DescribeWorkflowExecutionResponse{
|
|
WorkflowExecutionInfo: &workflowpb.WorkflowExecutionInfo{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: jobID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_RUNNING,
|
|
ExecutionTime: now,
|
|
Memo: &commonpb.Memo{
|
|
Fields: map[string]*commonpb.Payload{
|
|
batcher.BatchOperationTypeMemo: payload.EncodeString(batcher.BatchTypeTerminate),
|
|
},
|
|
},
|
|
SearchAttributes: nil,
|
|
},
|
|
PendingActivities: []*workflowpb.PendingActivityInfo{
|
|
{
|
|
HeartbeatDetails: hbdPayload,
|
|
},
|
|
},
|
|
}, nil
|
|
},
|
|
)
|
|
request := &workflowservice.DescribeBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobID,
|
|
}
|
|
|
|
resp, err := wh.DescribeBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
s.Equal(jobID, resp.GetJobId())
|
|
s.Equal(now, resp.GetStartTime())
|
|
s.Equal(now, resp.GetCloseTime())
|
|
s.Equal(enumspb.BATCH_OPERATION_TYPE_TERMINATE, resp.GetOperationType())
|
|
s.Equal(enumspb.BATCH_OPERATION_STATE_RUNNING, resp.GetState())
|
|
s.Assert().Equal(int64(5), resp.TotalOperationCount)
|
|
s.Assert().Equal(int64(3), resp.CompleteOperationCount)
|
|
s.Assert().Equal(int64(1), resp.FailureOperationCount)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeBatchOperation_FailedStatus() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
jobID := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
now := timestamppb.New(time.Now())
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockHistoryClient.EXPECT().DescribeWorkflowExecution(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *historyservice.DescribeWorkflowExecutionRequest,
|
|
_ ...grpc.CallOption,
|
|
) (*historyservice.DescribeWorkflowExecutionResponse, error) {
|
|
|
|
return &historyservice.DescribeWorkflowExecutionResponse{
|
|
WorkflowExecutionInfo: &workflowpb.WorkflowExecutionInfo{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: jobID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT,
|
|
ExecutionTime: now,
|
|
Memo: &commonpb.Memo{
|
|
Fields: map[string]*commonpb.Payload{
|
|
batcher.BatchOperationTypeMemo: payload.EncodeString(batcher.BatchTypeTerminate),
|
|
},
|
|
},
|
|
SearchAttributes: nil,
|
|
},
|
|
}, nil
|
|
},
|
|
)
|
|
request := &workflowservice.DescribeBatchOperationRequest{
|
|
Namespace: testNamespace.String(),
|
|
JobId: jobID,
|
|
}
|
|
|
|
resp, err := wh.DescribeBatchOperation(context.Background(), request)
|
|
s.NoError(err)
|
|
s.Equal(jobID, resp.GetJobId())
|
|
s.Equal(now, resp.GetStartTime())
|
|
s.Equal(now, resp.GetCloseTime())
|
|
s.Equal(enumspb.BATCH_OPERATION_TYPE_TERMINATE, resp.GetOperationType())
|
|
s.Equal(enumspb.BATCH_OPERATION_STATE_FAILED, resp.GetState())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestDescribeBatchOperation_InvalidRequest() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
request := &workflowservice.DescribeBatchOperationRequest{
|
|
Namespace: "",
|
|
JobId: uuid.NewString(),
|
|
}
|
|
var invalidArgumentErr *serviceerror.InvalidArgument
|
|
_, err := wh.DescribeBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
|
|
request.Namespace = uuid.NewString()
|
|
request.JobId = ""
|
|
_, err = wh.DescribeBatchOperation(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListBatchOperations() {
|
|
testNamespace := namespace.Name("test-namespace")
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
jobID := uuid.NewString()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
now := timestamppb.New(time.Now())
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().GetReadStoreName(testNamespace).Return("").AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().ListWorkflowExecutions(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
func(
|
|
_ context.Context,
|
|
request *manager.ListWorkflowExecutionsRequestV2,
|
|
) (*manager.ListWorkflowExecutionsResponse, error) {
|
|
s.Contains(request.Query, sadefs.TemporalNamespaceDivision)
|
|
return &manager.ListWorkflowExecutionsResponse{
|
|
Executions: []*workflowpb.WorkflowExecutionInfo{
|
|
{Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: jobID,
|
|
},
|
|
StartTime: now,
|
|
CloseTime: now,
|
|
Status: enumspb.WORKFLOW_EXECUTION_STATUS_TIMED_OUT,
|
|
ExecutionTime: now,
|
|
Memo: &commonpb.Memo{
|
|
Fields: map[string]*commonpb.Payload{
|
|
batcher.BatchOperationTypeMemo: payload.EncodeString(batcher.BatchTypeTerminate),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
},
|
|
)
|
|
request := &workflowservice.ListBatchOperationsRequest{
|
|
Namespace: testNamespace.String(),
|
|
}
|
|
|
|
resp, err := wh.ListBatchOperations(context.Background(), request)
|
|
s.NoError(err)
|
|
s.Equal(1, len(resp.OperationInfo))
|
|
s.Equal(jobID, resp.OperationInfo[0].GetJobId())
|
|
s.Equal(now, resp.OperationInfo[0].GetStartTime())
|
|
s.Equal(now, resp.OperationInfo[0].GetCloseTime())
|
|
s.Equal(enumspb.BATCH_OPERATION_STATE_FAILED, resp.OperationInfo[0].GetState())
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestListBatchOperations_InvalidRerquest() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
request := &workflowservice.ListBatchOperationsRequest{
|
|
Namespace: "",
|
|
}
|
|
var invalidArgumentErr *serviceerror.InvalidArgument
|
|
_, err := wh.ListBatchOperations(context.Background(), request)
|
|
s.ErrorAs(err, &invalidArgumentErr)
|
|
}
|
|
|
|
// This test is to make sure that GetWorkflowExecutionHistory returns the correct history when history service sends
|
|
// History events in the field response.History. This happens when history.sendRawHistoryBetweenInternalServices is enabled.
|
|
// This test verifies that HistoryEventFilterType is applied and EVENT_TYPE_WORKFLOW_EXECUTION_FAILED is converted to
|
|
// EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW for older SDKs.
|
|
func (s *WorkflowHandlerSuite) TestGetWorkflowExecutionHistory_InternalRawHistoryEnabled() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
we := commonpb.WorkflowExecution{WorkflowId: "wid1", RunId: uuid.New().String()}
|
|
newRunID := uuid.New().String()
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(tests.Namespace).Return(tests.NamespaceID, nil).Times(2)
|
|
s.mockSearchAttributesProvider.EXPECT().GetSearchAttributes(gomock.Any(), gomock.Any()).Return(searchattribute.TestNameTypeMap(), nil).Times(2)
|
|
|
|
req := &workflowservice.GetWorkflowExecutionHistoryRequest{
|
|
Namespace: tests.Namespace.String(),
|
|
Execution: &we,
|
|
MaximumPageSize: 10,
|
|
HistoryEventFilterType: enumspb.HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT,
|
|
SkipArchival: true,
|
|
}
|
|
s.mockHistoryClient.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), &historyservice.GetWorkflowExecutionHistoryRequest{
|
|
NamespaceId: tests.NamespaceID.String(),
|
|
Request: req,
|
|
}).Return(&historyservice.GetWorkflowExecutionHistoryResponse{
|
|
Response: &workflowservice.GetWorkflowExecutionHistoryResponse{
|
|
History: &historypb.History{},
|
|
},
|
|
History: &historypb.History{
|
|
Events: []*historypb.HistoryEvent{
|
|
{
|
|
EventId: int64(5),
|
|
EventType: enumspb.EVENT_TYPE_WORKFLOW_TASK_FAILED,
|
|
Attributes: &historypb.HistoryEvent_WorkflowTaskFailedEventAttributes{
|
|
WorkflowTaskFailedEventAttributes: &historypb.WorkflowTaskFailedEventAttributes{
|
|
Failure: &failurepb.Failure{Message: "this workflow task failed"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
EventId: int64(5),
|
|
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED,
|
|
Attributes: &historypb.HistoryEvent_WorkflowExecutionFailedEventAttributes{
|
|
WorkflowExecutionFailedEventAttributes: &historypb.WorkflowExecutionFailedEventAttributes{
|
|
Failure: &failurepb.Failure{Message: "this workflow failed"},
|
|
RetryState: enumspb.RETRY_STATE_IN_PROGRESS,
|
|
WorkflowTaskCompletedEventId: 4,
|
|
NewExecutionRunId: newRunID,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil).Times(2)
|
|
|
|
oldGoSDKVersion := "1.9.1"
|
|
newGoSDKVersion := "1.10.1"
|
|
|
|
// new sdk: should see failed event
|
|
ctx := headers.SetVersionsForTests(context.Background(), newGoSDKVersion, headers.ClientNameGoSDK, headers.SupportedServerVersions, headers.AllFeatures)
|
|
resp, err := wh.GetWorkflowExecutionHistory(ctx, req)
|
|
s.NoError(err)
|
|
s.False(resp.Archived)
|
|
event := resp.History.Events[0]
|
|
s.Equal(int64(5), event.EventId)
|
|
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED, event.EventType)
|
|
attrs := event.GetWorkflowExecutionFailedEventAttributes()
|
|
s.Equal("this workflow failed", attrs.Failure.Message)
|
|
s.Equal(newRunID, attrs.NewExecutionRunId)
|
|
s.Equal(enumspb.RETRY_STATE_IN_PROGRESS, attrs.RetryState)
|
|
|
|
// old sdk: should see continued-as-new event
|
|
// TODO: We can remove this once we no longer support SDK versions prior to around September 2021.
|
|
// See comment in workflowHandler.go:GetWorkflowExecutionHistory
|
|
ctx = headers.SetVersionsForTests(context.Background(), oldGoSDKVersion, headers.ClientNameGoSDK, headers.SupportedServerVersions, "")
|
|
resp, err = wh.GetWorkflowExecutionHistory(ctx, req)
|
|
s.NoError(err)
|
|
s.False(resp.Archived)
|
|
event = resp.History.Events[0]
|
|
s.Equal(int64(5), event.EventId)
|
|
s.Equal(enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW, event.EventType)
|
|
attrs2 := event.GetWorkflowExecutionContinuedAsNewEventAttributes()
|
|
s.Equal(newRunID, attrs2.NewExecutionRunId)
|
|
s.Equal("this workflow failed", attrs2.Failure.Message)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestValidateTimeSkippingConfig() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
var unimplementedErr *serviceerror.Unimplemented
|
|
|
|
// nil config is valid
|
|
s.Require().NoError(wh.validateTimeSkippingConfig(nil, s.testNamespace))
|
|
|
|
// config with enabled=false but dynamic config disabled returns error
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
s.Require().ErrorAs(wh.validateTimeSkippingConfig(&workflowpb.TimeSkippingConfig{Enabled: false}, s.testNamespace), &unimplementedErr)
|
|
|
|
// config with enabled=true but dynamic config disabled returns error
|
|
s.Require().ErrorAs(wh.validateTimeSkippingConfig(&workflowpb.TimeSkippingConfig{Enabled: true}, s.testNamespace), &unimplementedErr)
|
|
|
|
// config with enabled=false and dynamic config enabled is valid
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
s.Require().NoError(wh.validateTimeSkippingConfig(&workflowpb.TimeSkippingConfig{Enabled: false}, s.testNamespace))
|
|
|
|
// config with enabled=true and dynamic config enabled is valid
|
|
s.Require().NoError(wh.validateTimeSkippingConfig(&workflowpb.TimeSkippingConfig{Enabled: true}, s.testNamespace))
|
|
}
|
|
|
|
// TestExecuteMultiOperation_TimeSkipping_DCDisabled verifies that when the DC gate is off,
|
|
// a Start-with-time-skipping inside ExecuteMultiOperation is rejected. The error is wrapped
|
|
// as a MultiOperationExecution error with the per-operation InvalidArgument at index 0.
|
|
func (s *WorkflowHandlerSuite) TestExecuteMultiOperation_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
// Namespace lookup happens before operation validation.
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil)
|
|
// The SA mapper is called inside prepareStartWorkflowRequest before validateTimeSkippingConfig.
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
|
|
_, err := wh.ExecuteMultiOperation(context.Background(), &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
{
|
|
Operation: &workflowservice.ExecuteMultiOperationRequest_Operation_StartWorkflow{
|
|
StartWorkflow: &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Operation: &workflowservice.ExecuteMultiOperationRequest_Operation_UpdateWorkflow{
|
|
UpdateWorkflow: &workflowservice.UpdateWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: "WORKFLOW_ID"},
|
|
Request: &updatepb.Request{
|
|
Meta: &updatepb.Meta{UpdateId: "UPDATE_ID"},
|
|
Input: &updatepb.Input{Name: "NAME"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
s.Equal("Update-with-Start could not be executed.", err.Error())
|
|
var multiOpErr *serviceerror.MultiOperationExecution
|
|
s.ErrorAs(err, &multiOpErr)
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(multiOpErr.OperationErrors()[0], &unimplemented)
|
|
s.ErrorContains(multiOpErr.OperationErrors()[0], "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
// TestExecuteMultiOperation_TimeSkipping_DCEnabled verifies that when the DC gate is on,
|
|
// a Start-with-time-skipping inside ExecuteMultiOperation is accepted and the config is
|
|
// forwarded to the history client inside the StartWorkflow request.
|
|
func (s *WorkflowHandlerSuite) TestExecuteMultiOperation_TimeSkipping_DCEnabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil)
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
s.mockHistoryClient.EXPECT().ExecuteMultiOperation(gomock.Any(), mock.MatchedBy(func(req *historyservice.ExecuteMultiOperationRequest) bool {
|
|
startOp := req.GetOperations()[0].GetStartWorkflow()
|
|
return startOp.GetStartRequest().GetTimeSkippingConfig().GetEnabled()
|
|
})).Return(&historyservice.ExecuteMultiOperationResponse{
|
|
Responses: []*historyservice.ExecuteMultiOperationResponse_Response{
|
|
{
|
|
Response: &historyservice.ExecuteMultiOperationResponse_Response_StartWorkflow{
|
|
StartWorkflow: &historyservice.StartWorkflowExecutionResponse{},
|
|
},
|
|
},
|
|
{
|
|
Response: &historyservice.ExecuteMultiOperationResponse_Response_UpdateWorkflow{
|
|
UpdateWorkflow: &historyservice.UpdateWorkflowExecutionResponse{},
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
_, err := wh.ExecuteMultiOperation(context.Background(), &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
{
|
|
Operation: &workflowservice.ExecuteMultiOperationRequest_Operation_StartWorkflow{
|
|
StartWorkflow: &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Operation: &workflowservice.ExecuteMultiOperationRequest_Operation_UpdateWorkflow{
|
|
UpdateWorkflow: &workflowservice.UpdateWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: "WORKFLOW_ID"},
|
|
Request: &updatepb.Request{
|
|
Meta: &updatepb.Meta{UpdateId: "UPDATE_ID"},
|
|
Input: &updatepb.Input{Name: "NAME"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
s.NoError(err)
|
|
}
|
|
|
|
// TestStartWorkflowExecution_TimeSkipping_DCDisabled verifies that when the DC gate is off,
|
|
// a StartWorkflowExecution with a TimeSkippingConfig is rejected.
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
}
|
|
|
|
_, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(err, &unimplemented)
|
|
s.ErrorContains(err, "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
// TestStartWorkflowExecution_TimeSkipping_DCEnabled verifies that when the DC gate is on,
|
|
// a StartWorkflowExecution with a TimeSkippingConfig is accepted and the config is
|
|
// forwarded to the history client inside the StartWorkflow request.
|
|
func (s *WorkflowHandlerSuite) TestStartWorkflowExecution_TimeSkipping_DCEnabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil)
|
|
s.mockHistoryClient.EXPECT().StartWorkflowExecution(gomock.Any(), mock.MatchedBy(func(req *historyservice.StartWorkflowExecutionRequest) bool {
|
|
return req.StartRequest.GetTimeSkippingConfig().GetEnabled()
|
|
})).Return(&historyservice.StartWorkflowExecutionResponse{Started: true}, nil)
|
|
|
|
req := &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
}
|
|
|
|
resp, err := wh.StartWorkflowExecution(context.Background(), req)
|
|
s.NoError(err)
|
|
s.True(resp.Started)
|
|
}
|
|
|
|
// TestSignalWithStartWorkflowExecution_TimeSkipping_DCDisabled verifies that when the DC gate is off,
|
|
// a SignalWithStartWorkflowExecution with a TimeSkippingConfig is rejected.
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
SignalName: "signal-name",
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
}
|
|
|
|
_, err := wh.SignalWithStartWorkflowExecution(context.Background(), req)
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(err, &unimplemented)
|
|
s.ErrorContains(err, "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
// TestSignalWithStartWorkflowExecution_TimeSkipping_DCEnabled verifies that when the DC gate is on,
|
|
// a SignalWithStartWorkflowExecution with a TimeSkippingConfig is accepted and the config is
|
|
// forwarded to the history client inside the request.
|
|
func (s *WorkflowHandlerSuite) TestSignalWithStartWorkflowExecution_TimeSkipping_DCEnabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
wh := s.getWorkflowHandler(config)
|
|
s.mockSearchAttributesMapperProvider.EXPECT().GetMapper(gomock.Any()).Return(nil, nil)
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil)
|
|
s.mockHistoryClient.EXPECT().SignalWithStartWorkflowExecution(gomock.Any(), mock.MatchedBy(func(req *historyservice.SignalWithStartWorkflowExecutionRequest) bool {
|
|
return req.SignalWithStartRequest.GetTimeSkippingConfig().GetEnabled()
|
|
})).Return(&historyservice.SignalWithStartWorkflowExecutionResponse{Started: true}, nil)
|
|
|
|
req := &workflowservice.SignalWithStartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
SignalName: "signal-name",
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
}
|
|
|
|
resp, err := wh.SignalWithStartWorkflowExecution(context.Background(), req)
|
|
s.NoError(err)
|
|
s.True(resp.Started)
|
|
}
|
|
|
|
// TestResetWorkflowExecution_TimeSkipping_DCDisabled verifies that when the DC gate is off,
|
|
// a ResetWorkflowExecution request with a TimeSkippingConfig inside PostResetOperations is rejected.
|
|
func (s *WorkflowHandlerSuite) TestResetWorkflowExecution_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
_, err := wh.ResetWorkflowExecution(context.Background(), &workflowservice.ResetWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
RequestId: uuid.NewString(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
RunId: uuid.NewString(),
|
|
},
|
|
PostResetOperations: []*workflowpb.PostResetOperation{
|
|
{
|
|
Variant: &workflowpb.PostResetOperation_UpdateWorkflowOptions_{
|
|
UpdateWorkflowOptions: &workflowpb.PostResetOperation_UpdateWorkflowOptions{
|
|
WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"time_skipping_config"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(err, &unimplemented)
|
|
s.ErrorContains(err, "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
// TestStartBatchOperation_ResetOperation_TimeSkipping_DCDisabled verifies that when the DC gate
|
|
// is off, a batch reset with a TimeSkippingConfig inside PostResetOperations is rejected.
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_ResetOperation_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
|
|
_, err := wh.StartBatchOperation(context.Background(), &workflowservice.StartBatchOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
JobId: uuid.NewString(),
|
|
Reason: "test",
|
|
VisibilityQuery: "WorkflowId='test'",
|
|
Operation: &workflowservice.StartBatchOperationRequest_ResetOperation{
|
|
ResetOperation: &batchpb.BatchOperationReset{
|
|
Options: &commonpb.ResetOptions{
|
|
Target: &commonpb.ResetOptions_WorkflowTaskId{WorkflowTaskId: 1},
|
|
},
|
|
PostResetOperations: []*workflowpb.PostResetOperation{
|
|
{
|
|
Variant: &workflowpb.PostResetOperation_UpdateWorkflowOptions_{
|
|
UpdateWorkflowOptions: &workflowpb.PostResetOperation_UpdateWorkflowOptions{
|
|
WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"time_skipping_config"}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(err, &unimplemented)
|
|
s.ErrorContains(err, "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
// TestStartBatchOperation_UpdateWorkflowOptionsOperation_TimeSkipping_DCDisabled verifies that
|
|
// when the DC gate is off, a batch UpdateWorkflowOptions with a TimeSkippingConfig is rejected.
|
|
func (s *WorkflowHandlerSuite) TestStartBatchOperation_UpdateWorkflowOptionsOperation_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
namespaceID := namespace.ID(uuid.NewString())
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Any()).Return(namespaceID, nil).AnyTimes()
|
|
s.mockVisibilityMgr.EXPECT().CountWorkflowExecutions(gomock.Any(), gomock.Any()).Return(&manager.CountWorkflowExecutionsResponse{Count: 0}, nil)
|
|
|
|
_, err := wh.StartBatchOperation(context.Background(), &workflowservice.StartBatchOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
JobId: uuid.NewString(),
|
|
Reason: "test",
|
|
VisibilityQuery: "WorkflowId='test'",
|
|
Operation: &workflowservice.StartBatchOperationRequest_UpdateWorkflowOptionsOperation{
|
|
UpdateWorkflowOptionsOperation: &batchpb.BatchOperationUpdateWorkflowExecutionOptions{
|
|
WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"time_skipping_config"}},
|
|
},
|
|
},
|
|
})
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(err, &unimplemented)
|
|
s.ErrorContains(err, "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) newConfig() *Config {
|
|
return NewConfig(dc.NewNoopCollection(), numHistoryShards)
|
|
}
|
|
|
|
func updateRequest(
|
|
historyArchivalURI string,
|
|
historyArchivalState enumspb.ArchivalState,
|
|
visibilityArchivalURI string,
|
|
visibilityArchivalState enumspb.ArchivalState,
|
|
) *workflowservice.UpdateNamespaceRequest {
|
|
return &workflowservice.UpdateNamespaceRequest{
|
|
Namespace: "test-name",
|
|
Config: &namespacepb.NamespaceConfig{
|
|
HistoryArchivalState: historyArchivalState,
|
|
HistoryArchivalUri: historyArchivalURI,
|
|
VisibilityArchivalState: visibilityArchivalState,
|
|
VisibilityArchivalUri: visibilityArchivalURI,
|
|
},
|
|
}
|
|
}
|
|
|
|
func persistenceGetNamespaceResponse(historyArchivalState, visibilityArchivalState *namespace.ArchivalConfigState) *persistence.GetNamespaceResponse {
|
|
return &persistence.GetNamespaceResponse{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: &persistencespb.NamespaceInfo{
|
|
Id: testNamespaceID,
|
|
Name: "test-name",
|
|
State: 0,
|
|
Description: "test-description",
|
|
Owner: "test-owner-email",
|
|
Data: make(map[string]string),
|
|
},
|
|
Config: &persistencespb.NamespaceConfig{
|
|
Retention: timestamp.DurationFromDays(1),
|
|
HistoryArchivalState: historyArchivalState.State,
|
|
HistoryArchivalUri: historyArchivalState.URI,
|
|
VisibilityArchivalState: visibilityArchivalState.State,
|
|
VisibilityArchivalUri: visibilityArchivalState.URI,
|
|
},
|
|
ReplicationConfig: &persistencespb.NamespaceReplicationConfig{
|
|
ActiveClusterName: cluster.TestCurrentClusterName,
|
|
Clusters: []string{
|
|
cluster.TestCurrentClusterName,
|
|
},
|
|
},
|
|
ConfigVersion: 0,
|
|
FailoverVersion: 0,
|
|
FailoverNotificationVersion: 0,
|
|
},
|
|
IsGlobalNamespace: false,
|
|
NotificationVersion: 0,
|
|
}
|
|
}
|
|
|
|
func registerNamespaceRequest(
|
|
historyArchivalState enumspb.ArchivalState,
|
|
historyArchivalURI string,
|
|
visibilityArchivalState enumspb.ArchivalState,
|
|
visibilityArchivalURI string,
|
|
) *workflowservice.RegisterNamespaceRequest {
|
|
return &workflowservice.RegisterNamespaceRequest{
|
|
Namespace: "test-namespace",
|
|
Description: "test-description",
|
|
OwnerEmail: "test-owner-email",
|
|
WorkflowExecutionRetentionPeriod: durationpb.New(10 * time.Hour * 24),
|
|
Clusters: []*replicationpb.ClusterReplicationConfig{
|
|
{
|
|
ClusterName: cluster.TestCurrentClusterName,
|
|
},
|
|
},
|
|
ActiveClusterName: cluster.TestCurrentClusterName,
|
|
Data: make(map[string]string),
|
|
HistoryArchivalState: historyArchivalState,
|
|
HistoryArchivalUri: historyArchivalURI,
|
|
VisibilityArchivalState: visibilityArchivalState,
|
|
VisibilityArchivalUri: visibilityArchivalURI,
|
|
IsGlobalNamespace: false,
|
|
}
|
|
}
|
|
|
|
func getHistoryRequest(nextPageToken []byte) *workflowservice.GetWorkflowExecutionHistoryRequest {
|
|
return &workflowservice.GetWorkflowExecutionHistoryRequest{
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: testWorkflowID,
|
|
RunId: testRunID,
|
|
},
|
|
NextPageToken: nextPageToken,
|
|
}
|
|
}
|
|
|
|
func listArchivedWorkflowExecutionsTestRequest() *workflowservice.ListArchivedWorkflowExecutionsRequest {
|
|
return &workflowservice.ListArchivedWorkflowExecutionsRequest{
|
|
Namespace: "some random namespace name",
|
|
PageSize: 10,
|
|
Query: "some random query string",
|
|
}
|
|
}
|
|
|
|
func TestContextNearDeadline(t *testing.T) {
|
|
assert.False(t, contextNearDeadline(context.Background(), longPollTailRoom))
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
|
|
defer cancel()
|
|
assert.True(t, contextNearDeadline(ctx, longPollTailRoom))
|
|
assert.False(t, contextNearDeadline(ctx, time.Millisecond))
|
|
}
|
|
|
|
func TestValidateRequestId(t *testing.T) {
|
|
req := workflowservice.StartWorkflowExecutionRequest{RequestId: ""}
|
|
err := validateRequestId(&req.RequestId, 100)
|
|
assert.Nil(t, err)
|
|
assert.Len(t, req.RequestId, 36) // new UUID length
|
|
}
|
|
|
|
func TestDedupLinksFromCallbacks(t *testing.T) {
|
|
links := []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_EventRef{
|
|
EventRef: &commonpb.Link_WorkflowEvent_EventReference{
|
|
EventId: 3,
|
|
EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_EventRef{
|
|
EventRef: &commonpb.Link_WorkflowEvent_EventReference{
|
|
EventId: 5,
|
|
EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_RequestIdRef{
|
|
RequestIdRef: &commonpb.Link_WorkflowEvent_RequestIdReference{
|
|
RequestId: "test-request-id",
|
|
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_OPTIONS_UPDATED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
callbacks := []*commonpb.Callback{
|
|
{
|
|
Variant: &commonpb.Callback_Nexus_{
|
|
Nexus: &commonpb.Callback_Nexus{},
|
|
},
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_EventRef{
|
|
EventRef: &commonpb.Link_WorkflowEvent_EventReference{
|
|
EventId: 3,
|
|
EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_EventRef{
|
|
EventRef: &commonpb.Link_WorkflowEvent_EventReference{
|
|
EventId: 5,
|
|
EventType: enumspb.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Variant: &commonpb.Callback_Internal_{
|
|
Internal: &commonpb.Callback_Internal{},
|
|
},
|
|
Links: []*commonpb.Link{
|
|
{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_RequestIdRef{
|
|
RequestIdRef: &commonpb.Link_WorkflowEvent_RequestIdReference{
|
|
RequestId: "test-request-id",
|
|
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_OPTIONS_UPDATED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
dedupedLinks := dedupLinksFromCallbacks(links, callbacks)
|
|
assert.Len(t, dedupedLinks, 1)
|
|
protoassert.ProtoEqual(
|
|
t,
|
|
&commonpb.Link{
|
|
Variant: &commonpb.Link_WorkflowEvent_{
|
|
WorkflowEvent: &commonpb.Link_WorkflowEvent{
|
|
Namespace: "test-ns",
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "test-run-id",
|
|
Reference: &commonpb.Link_WorkflowEvent_RequestIdRef{
|
|
RequestIdRef: &commonpb.Link_WorkflowEvent_RequestIdReference{
|
|
RequestId: "test-request-id",
|
|
EventType: enumspb.EVENT_TYPE_WORKFLOW_EXECUTION_OPTIONS_UPDATED,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
dedupedLinks[0],
|
|
)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) Test_DeleteWorkflowExecution() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
type test struct {
|
|
Name string
|
|
Request *workflowservice.DeleteWorkflowExecutionRequest
|
|
Expected error
|
|
}
|
|
// request validation tests
|
|
testCases1 := []test{
|
|
{
|
|
Name: "nil request",
|
|
Request: nil,
|
|
Expected: &serviceerror.InvalidArgument{Message: "Request is nil."},
|
|
},
|
|
{
|
|
Name: "empty request",
|
|
Request: &workflowservice.DeleteWorkflowExecutionRequest{},
|
|
Expected: &serviceerror.InvalidArgument{Message: "Execution is not set on request."},
|
|
},
|
|
{
|
|
Name: "empty namespace",
|
|
Request: &workflowservice.DeleteWorkflowExecutionRequest{
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "wrong-run-id",
|
|
},
|
|
},
|
|
Expected: &serviceerror.InvalidArgument{Message: "Invalid RunId."},
|
|
},
|
|
}
|
|
for _, testCase := range testCases1 {
|
|
s.T().Run(testCase.Name, func(t *testing.T) {
|
|
resp, err := wh.DeleteWorkflowExecution(ctx, testCase.Request)
|
|
s.Equal(testCase.Expected, err)
|
|
s.Nil(resp)
|
|
})
|
|
}
|
|
|
|
// History call failed.
|
|
s.mockResource.HistoryClient.EXPECT().DeleteWorkflowExecution(gomock.Any(), gomock.Any()).Return(nil, errors.New("random error"))
|
|
s.mockResource.NamespaceCache.EXPECT().GetNamespaceID(namespace.Name("test-namespace")).Return(namespace.ID("test-namespace-id"), nil)
|
|
resp, err := wh.DeleteWorkflowExecution(ctx, &workflowservice.DeleteWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "test-workflow-id",
|
|
RunId: "d2595cb3-3b21-4026-a3e8-17bc32fb2a2b",
|
|
},
|
|
})
|
|
s.Error(err)
|
|
s.Equal("random error", err.Error())
|
|
s.Nil(resp)
|
|
|
|
// Success case.
|
|
s.mockResource.HistoryClient.EXPECT().DeleteWorkflowExecution(gomock.Any(), gomock.Any()).Return(&historyservice.DeleteWorkflowExecutionResponse{}, nil)
|
|
s.mockResource.NamespaceCache.EXPECT().GetNamespaceID(namespace.Name("test-namespace")).Return(namespace.ID("test-namespace-id"), nil)
|
|
resp, err = wh.DeleteWorkflowExecution(ctx, &workflowservice.DeleteWorkflowExecutionRequest{
|
|
Namespace: "test-namespace",
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "test-workflow-id",
|
|
// RunId is not required.
|
|
},
|
|
})
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestExecuteMultiOperation() {
|
|
ctx := context.Background()
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.mockResource.NamespaceCache.EXPECT().
|
|
GetNamespaceID(namespace.Name(s.testNamespace.String())).Return(s.testNamespaceID, nil).AnyTimes()
|
|
s.mockSearchAttributesMapperProvider.EXPECT().
|
|
GetMapper(gomock.Any()).Return(nil, nil).AnyTimes()
|
|
|
|
newStartOp := func(op *workflowservice.StartWorkflowExecutionRequest) *workflowservice.ExecuteMultiOperationRequest_Operation {
|
|
return &workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
Operation: &workflowservice.ExecuteMultiOperationRequest_Operation_StartWorkflow{
|
|
StartWorkflow: op,
|
|
},
|
|
}
|
|
}
|
|
validStartReq := func() *workflowservice.StartWorkflowExecutionRequest {
|
|
return &workflowservice.StartWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowId: "WORKFLOW_ID",
|
|
WorkflowType: &commonpb.WorkflowType{Name: "workflow-type"},
|
|
TaskQueue: &taskqueuepb.TaskQueue{Name: "task-queue"},
|
|
}
|
|
}
|
|
newUpdateOp := func(op *workflowservice.UpdateWorkflowExecutionRequest) *workflowservice.ExecuteMultiOperationRequest_Operation {
|
|
return &workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
Operation: &workflowservice.ExecuteMultiOperationRequest_Operation_UpdateWorkflow{
|
|
UpdateWorkflow: op,
|
|
},
|
|
}
|
|
}
|
|
validUpdateReq := func() *workflowservice.UpdateWorkflowExecutionRequest {
|
|
return &workflowservice.UpdateWorkflowExecutionRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{WorkflowId: "WORKFLOW_ID"},
|
|
Request: &updatepb.Request{
|
|
Meta: &updatepb.Meta{UpdateId: "UPDATE_ID"},
|
|
Input: &updatepb.Input{Name: "NAME"},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NOTE: functional tests are testing the happy case
|
|
|
|
s.Run("operations list that is not [Start, Update] is invalid", func() {
|
|
// empty list
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
})
|
|
|
|
s.Nil(resp)
|
|
s.Equal(errMultiOpNotStartAndUpdate, err)
|
|
|
|
// 1 item
|
|
resp, err = wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{newStartOp(nil)},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
s.Equal(errMultiOpNotStartAndUpdate, err)
|
|
|
|
// 3 items
|
|
resp, err = wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(nil), newStartOp(nil), newStartOp(nil),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
s.Equal(errMultiOpNotStartAndUpdate, err)
|
|
|
|
// 2 undefined operations
|
|
resp, err = wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
{},
|
|
{},
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
s.Equal(errMultiOpNotStartAndUpdate, err)
|
|
|
|
// 2 Starts
|
|
resp, err = wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(validStartReq()), newStartOp(validStartReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
s.Equal(errMultiOpNotStartAndUpdate, err)
|
|
|
|
// 2 Updates
|
|
resp, err = wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newUpdateOp(validUpdateReq()), newUpdateOp(validUpdateReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
s.Equal(errMultiOpNotStartAndUpdate, err)
|
|
})
|
|
|
|
assertMultiOpsErr := func(expectedErrs []error, actual error) {
|
|
s.Equal("Update-with-Start could not be executed.", actual.Error())
|
|
s.EqualValues(expectedErrs, actual.(*serviceerror.MultiOperationExecution).OperationErrors())
|
|
}
|
|
|
|
s.Run("operation with different workflow ID as previous operation is invalid", func() {
|
|
updateReq := validUpdateReq()
|
|
updateReq.WorkflowExecution.WorkflowId = "foo"
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(validStartReq()),
|
|
newUpdateOp(updateReq),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr([]error{errMultiOpAborted, errMultiOpWorkflowIdInconsistent}, err)
|
|
})
|
|
|
|
s.Run("Start operation is validated", func() {
|
|
// expecting the same validation as for standalone Start operation; only testing one here:
|
|
s.Run("requires workflow id", func() {
|
|
startReq := validStartReq()
|
|
startReq.WorkflowId = ""
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(startReq),
|
|
newUpdateOp(validUpdateReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr([]error{workflow.ErrWorkflowIDNotSet, errMultiOpAborted}, err)
|
|
})
|
|
|
|
// unique to MultiOperation:
|
|
s.Run("`cron_schedule` is invalid", func() {
|
|
startReq := validStartReq()
|
|
startReq.CronSchedule = "0 */12 * * *"
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(startReq),
|
|
newUpdateOp(validUpdateReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr([]error{errMultiOpStartCronSchedule, errMultiOpAborted}, err)
|
|
})
|
|
|
|
// unique to MultiOperation:
|
|
s.Run("`request_eager_execution` is invalid", func() {
|
|
startReq := validStartReq()
|
|
startReq.RequestEagerExecution = true
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(startReq),
|
|
newUpdateOp(validUpdateReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr([]error{errMultiOpEagerWorkflow, errMultiOpAborted}, err)
|
|
})
|
|
|
|
// unique to MultiOperation:
|
|
s.Run("`workflow_start_delay` is invalid", func() {
|
|
startReq := validStartReq()
|
|
startReq.WorkflowStartDelay = durationpb.New(1 * time.Second)
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(startReq),
|
|
newUpdateOp(validUpdateReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr([]error{errMultiOpStartDelay, errMultiOpAborted}, err)
|
|
})
|
|
|
|
// unique to MultiOperation:
|
|
s.Run("namespace mismatch", func() {
|
|
startReq := validStartReq()
|
|
startReq.Namespace = "other-namespace"
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(startReq),
|
|
newUpdateOp(validUpdateReq()),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr(
|
|
[]error{
|
|
errMultiOpNamespaceMismatch,
|
|
errMultiOpAborted,
|
|
},
|
|
err,
|
|
)
|
|
})
|
|
})
|
|
|
|
s.Run("Update operation is validated", func() {
|
|
// expecting the same validation as for standalone Update operation; only testing a few of the validations here
|
|
s.Run("requires workflow id", func() {
|
|
updateReq := validUpdateReq()
|
|
updateReq.Request.Input = nil
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(validStartReq()),
|
|
newUpdateOp(updateReq),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr([]error{errMultiOpAborted, errUpdateInputNotSet}, err)
|
|
})
|
|
|
|
// unique to MultiOperation:
|
|
s.Run("namespace mismatch", func() {
|
|
updateReq := validUpdateReq()
|
|
updateReq.Namespace = "other-namespace"
|
|
|
|
resp, err := wh.ExecuteMultiOperation(ctx, &workflowservice.ExecuteMultiOperationRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Operations: []*workflowservice.ExecuteMultiOperationRequest_Operation{
|
|
newStartOp(validStartReq()),
|
|
newUpdateOp(updateReq),
|
|
},
|
|
})
|
|
|
|
s.Nil(resp)
|
|
assertMultiOpsErr(
|
|
[]error{
|
|
errMultiOpAborted,
|
|
errMultiOpNamespaceMismatch,
|
|
},
|
|
err,
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestShutdownWorker() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
stickyTaskQueue := "sticky-task-queue"
|
|
|
|
expectedMatchingRequest := &matchingservice.ForceUnloadTaskQueuePartitionRequest{
|
|
NamespaceId: s.testNamespaceID.String(),
|
|
TaskQueuePartition: &taskqueuespb.TaskQueuePartition{
|
|
TaskQueue: stickyTaskQueue,
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
},
|
|
}
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).AnyTimes()
|
|
s.mockMatchingClient.EXPECT().ForceUnloadTaskQueuePartition(gomock.Any(), gomock.Eq(expectedMatchingRequest)).Return(&matchingservice.ForceUnloadTaskQueuePartitionResponse{}, nil)
|
|
|
|
_, err := wh.ShutdownWorker(ctx, &workflowservice.ShutdownWorkerRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
StickyTaskQueue: stickyTaskQueue,
|
|
Identity: "worker",
|
|
Reason: "graceful shutdown",
|
|
})
|
|
if err != nil {
|
|
s.Fail("ShutdownWorker failed:", err)
|
|
}
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestShutdownWorkerWithEagerPollCancellation() {
|
|
config := s.newConfig()
|
|
config.EnableCancelWorkerPollsOnShutdown = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
config.NumTaskQueueReadPartitions = dc.GetIntPropertyFnFilteredByTaskQueue(2) // 2 partitions
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
stickyTaskQueue := "sticky-task-queue"
|
|
taskQueue := "my-task-queue"
|
|
workerInstanceKey := "worker-instance-123"
|
|
|
|
// Expect cancellation for 2 partitions x 2 task types = 4 calls (in any order due to parallelization)
|
|
s.mockMatchingClient.EXPECT().CancelOutstandingWorkerPolls(gomock.Any(), gomock.Any()).
|
|
Return(&matchingservice.CancelOutstandingWorkerPollsResponse{CancelledCount: 1}, nil).
|
|
Times(4)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).AnyTimes()
|
|
|
|
expectedForceUnloadRequest := &matchingservice.ForceUnloadTaskQueuePartitionRequest{
|
|
NamespaceId: s.testNamespaceID.String(),
|
|
TaskQueuePartition: &taskqueuespb.TaskQueuePartition{
|
|
TaskQueue: stickyTaskQueue,
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
},
|
|
}
|
|
|
|
s.mockMatchingClient.EXPECT().ForceUnloadTaskQueuePartition(gomock.Any(), gomock.Eq(expectedForceUnloadRequest)).Return(&matchingservice.ForceUnloadTaskQueuePartitionResponse{}, nil)
|
|
|
|
_, err := wh.ShutdownWorker(ctx, &workflowservice.ShutdownWorkerRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
StickyTaskQueue: stickyTaskQueue,
|
|
Identity: "worker",
|
|
Reason: "graceful shutdown",
|
|
WorkerInstanceKey: workerInstanceKey,
|
|
TaskQueue: taskQueue,
|
|
})
|
|
if err != nil {
|
|
s.Fail("ShutdownWorker with eager poll cancellation failed:", err)
|
|
}
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestShutdownWorkerDeduplicatesByHost() {
|
|
// When multiple partitions route to the same matching host, only one RPC should be sent per host.
|
|
config := s.newConfig()
|
|
config.EnableCancelWorkerPollsOnShutdown = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
config.NumTaskQueueReadPartitions = dc.GetIntPropertyFnFilteredByTaskQueue(4) // 4 partitions
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
stickyTaskQueue := "sticky-task-queue"
|
|
taskQueue := "my-task-queue"
|
|
workerInstanceKey := "worker-instance-123"
|
|
|
|
// Wrap the mock matching client with a Route() that maps partitions to 2 hosts:
|
|
// root (partition 0) and partition 1 -> host-a, partitions 2 and 3 -> host-b.
|
|
routingClient := &routingMatchingClient{
|
|
MockMatchingServiceClient: s.mockMatchingClient,
|
|
routeFn: func(p tqid.Partition) (string, error) {
|
|
if strings.Contains(p.RpcName(), "/2") || strings.Contains(p.RpcName(), "/3") {
|
|
return "host-b", nil
|
|
}
|
|
return "host-a", nil
|
|
},
|
|
}
|
|
wh.matchingClient = routingClient
|
|
|
|
// 4 partitions across 2 hosts x 2 task types = 4 RPCs (not 8).
|
|
s.mockMatchingClient.EXPECT().CancelOutstandingWorkerPolls(gomock.Any(), gomock.Any()).
|
|
Return(&matchingservice.CancelOutstandingWorkerPollsResponse{CancelledCount: 1}, nil).
|
|
Times(4)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).AnyTimes()
|
|
|
|
expectedForceUnloadRequest := &matchingservice.ForceUnloadTaskQueuePartitionRequest{
|
|
NamespaceId: s.testNamespaceID.String(),
|
|
TaskQueuePartition: &taskqueuespb.TaskQueuePartition{
|
|
TaskQueue: stickyTaskQueue,
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
},
|
|
}
|
|
s.mockMatchingClient.EXPECT().ForceUnloadTaskQueuePartition(gomock.Any(), gomock.Eq(expectedForceUnloadRequest)).Return(&matchingservice.ForceUnloadTaskQueuePartitionResponse{}, nil)
|
|
|
|
_, err := wh.ShutdownWorker(ctx, &workflowservice.ShutdownWorkerRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
StickyTaskQueue: stickyTaskQueue,
|
|
Identity: "worker",
|
|
Reason: "graceful shutdown",
|
|
WorkerInstanceKey: workerInstanceKey,
|
|
TaskQueue: taskQueue,
|
|
})
|
|
s.NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestShutdownWorkerWithCancellationError() {
|
|
// Verifies graceful degradation: ShutdownWorker succeeds even when poll cancellation fails.
|
|
// This ensures backward compatibility during rolling upgrades.
|
|
config := s.newConfig()
|
|
config.EnableCancelWorkerPollsOnShutdown = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
config.NumTaskQueueReadPartitions = dc.GetIntPropertyFnFilteredByTaskQueue(1)
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
stickyTaskQueue := "sticky-task-queue"
|
|
taskQueue := "my-task-queue"
|
|
workerInstanceKey := "worker-instance-123"
|
|
|
|
// CancelOutstandingWorkerPolls returns an error (simulates old matching node)
|
|
s.mockMatchingClient.EXPECT().CancelOutstandingWorkerPolls(gomock.Any(), gomock.Any()).
|
|
Return(nil, serviceerror.NewUnimplemented("method not implemented")).
|
|
Times(2) // 1 partition x 2 task types
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).AnyTimes()
|
|
|
|
expectedForceUnloadRequest := &matchingservice.ForceUnloadTaskQueuePartitionRequest{
|
|
NamespaceId: s.testNamespaceID.String(),
|
|
TaskQueuePartition: &taskqueuespb.TaskQueuePartition{
|
|
TaskQueue: stickyTaskQueue,
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
},
|
|
}
|
|
|
|
s.mockMatchingClient.EXPECT().ForceUnloadTaskQueuePartition(gomock.Any(), gomock.Eq(expectedForceUnloadRequest)).Return(&matchingservice.ForceUnloadTaskQueuePartitionResponse{}, nil)
|
|
|
|
// ShutdownWorker should succeed despite cancellation errors
|
|
_, err := wh.ShutdownWorker(ctx, &workflowservice.ShutdownWorkerRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
StickyTaskQueue: stickyTaskQueue,
|
|
Identity: "worker",
|
|
Reason: "graceful shutdown",
|
|
WorkerInstanceKey: workerInstanceKey,
|
|
TaskQueue: taskQueue,
|
|
})
|
|
s.NoError(err, "ShutdownWorker should succeed even when poll cancellation fails")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestShutdownWorkerWithPartialCancellationFailure() {
|
|
// Verifies ShutdownWorker succeeds when some cancellation calls succeed and others fail.
|
|
config := s.newConfig()
|
|
config.EnableCancelWorkerPollsOnShutdown = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
config.NumTaskQueueReadPartitions = dc.GetIntPropertyFnFilteredByTaskQueue(2) // 2 partitions
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
stickyTaskQueue := "sticky-task-queue"
|
|
taskQueue := "my-task-queue"
|
|
workerInstanceKey := "worker-instance-123"
|
|
|
|
// Mixed results: some succeed, some fail (2 partitions x 2 task types = 4 calls)
|
|
s.mockMatchingClient.EXPECT().CancelOutstandingWorkerPolls(gomock.Any(), gomock.Any()).
|
|
Return(&matchingservice.CancelOutstandingWorkerPollsResponse{CancelledCount: 1}, nil).
|
|
Times(2)
|
|
s.mockMatchingClient.EXPECT().CancelOutstandingWorkerPolls(gomock.Any(), gomock.Any()).
|
|
Return(nil, serviceerror.NewUnavailable("temporary error")).
|
|
Times(2)
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).AnyTimes()
|
|
|
|
expectedForceUnloadRequest := &matchingservice.ForceUnloadTaskQueuePartitionRequest{
|
|
NamespaceId: s.testNamespaceID.String(),
|
|
TaskQueuePartition: &taskqueuespb.TaskQueuePartition{
|
|
TaskQueue: stickyTaskQueue,
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
},
|
|
}
|
|
|
|
s.mockMatchingClient.EXPECT().ForceUnloadTaskQueuePartition(gomock.Any(), gomock.Eq(expectedForceUnloadRequest)).Return(&matchingservice.ForceUnloadTaskQueuePartitionResponse{}, nil)
|
|
|
|
// ShutdownWorker should succeed despite partial failures
|
|
_, err := wh.ShutdownWorker(ctx, &workflowservice.ShutdownWorkerRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
StickyTaskQueue: stickyTaskQueue,
|
|
Identity: "worker",
|
|
Reason: "graceful shutdown",
|
|
WorkerInstanceKey: workerInstanceKey,
|
|
TaskQueue: taskQueue,
|
|
})
|
|
s.NoError(err, "ShutdownWorker should succeed even with partial cancellation failures")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestPatchSchedule_TriggerImmediatelyScheduledTime() {
|
|
config := s.newConfig()
|
|
config.EnableSchedules = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
config.EnableCHASMSchedulerRouting = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
scheduleID := "test-schedule-id"
|
|
requestID := "test-request-id"
|
|
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).AnyTimes()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
setupTrigger func() *schedulepb.TriggerImmediatelyRequest
|
|
expectScheduledTimeSet bool
|
|
expectHistoryClientCall bool
|
|
}{
|
|
{
|
|
name: "trigger with nil ScheduledTime should get timestamp set",
|
|
setupTrigger: func() *schedulepb.TriggerImmediatelyRequest {
|
|
return &schedulepb.TriggerImmediatelyRequest{
|
|
OverlapPolicy: enumspb.SCHEDULE_OVERLAP_POLICY_SKIP,
|
|
ScheduledTime: nil,
|
|
}
|
|
},
|
|
expectScheduledTimeSet: true,
|
|
expectHistoryClientCall: true,
|
|
},
|
|
{
|
|
name: "trigger with existing ScheduledTime should not be modified",
|
|
setupTrigger: func() *schedulepb.TriggerImmediatelyRequest {
|
|
existingTime := timestamppb.New(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC))
|
|
return &schedulepb.TriggerImmediatelyRequest{
|
|
OverlapPolicy: enumspb.SCHEDULE_OVERLAP_POLICY_SKIP,
|
|
ScheduledTime: existingTime,
|
|
}
|
|
},
|
|
expectScheduledTimeSet: false, // Should not modify existing time
|
|
expectHistoryClientCall: true,
|
|
},
|
|
{
|
|
name: "no trigger should not call history client",
|
|
setupTrigger: func() *schedulepb.TriggerImmediatelyRequest {
|
|
return nil
|
|
},
|
|
expectScheduledTimeSet: false,
|
|
expectHistoryClientCall: true, // Will still call but without trigger
|
|
},
|
|
}
|
|
|
|
for _, tt := range testCases {
|
|
s.Run(tt.name, func() {
|
|
trigger := tt.setupTrigger()
|
|
|
|
patch := &schedulepb.SchedulePatch{}
|
|
if trigger != nil {
|
|
patch.TriggerImmediately = trigger
|
|
}
|
|
|
|
request := &workflowservice.PatchScheduleRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
ScheduleId: scheduleID,
|
|
RequestId: requestID,
|
|
Patch: patch,
|
|
Identity: "test-identity",
|
|
}
|
|
|
|
// Capture the original ScheduledTime if it exists
|
|
var originalScheduledTime *timestamppb.Timestamp
|
|
if trigger != nil {
|
|
originalScheduledTime = trigger.ScheduledTime
|
|
}
|
|
|
|
if tt.expectHistoryClientCall {
|
|
s.mockHistoryClient.EXPECT().SignalWorkflowExecution(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
).Return(&historyservice.SignalWorkflowExecutionResponse{}, nil).Times(1)
|
|
}
|
|
|
|
beforeCall := time.Now()
|
|
resp, err := wh.PatchSchedule(ctx, request)
|
|
afterCall := time.Now()
|
|
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
|
|
if trigger != nil {
|
|
if tt.expectScheduledTimeSet {
|
|
// Verify that ScheduledTime was set and is recent
|
|
s.NotNil(trigger.ScheduledTime, "ScheduledTime should have been set")
|
|
s.Nil(originalScheduledTime, "Original ScheduledTime should have been nil")
|
|
|
|
scheduledTime := trigger.ScheduledTime.AsTime()
|
|
s.True(scheduledTime.After(beforeCall) || scheduledTime.Equal(beforeCall),
|
|
"ScheduledTime should be at or after the call start time")
|
|
s.True(scheduledTime.Before(afterCall) || scheduledTime.Equal(afterCall),
|
|
"ScheduledTime should be at or before the call end time")
|
|
} else {
|
|
// Verify that existing ScheduledTime was not modified
|
|
if originalScheduledTime != nil {
|
|
s.Equal(originalScheduledTime.AsTime(), trigger.ScheduledTime.AsTime(),
|
|
"Existing ScheduledTime should not have been modified")
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestPatchSchedule_ValidationAndErrors() {
|
|
config := s.newConfig()
|
|
config.EnableSchedules = dc.GetBoolPropertyFnFilteredByNamespace(true)
|
|
wh := s.getWorkflowHandler(config)
|
|
ctx := context.Background()
|
|
|
|
s.Run("nil request should return error", func() {
|
|
resp, err := wh.PatchSchedule(ctx, nil)
|
|
s.Nil(resp)
|
|
s.Equal(errRequestNotSet, err)
|
|
})
|
|
|
|
s.Run("schedules disabled should return error", func() {
|
|
disabledConfig := s.newConfig()
|
|
disabledConfig.EnableSchedules = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
disabledWh := s.getWorkflowHandler(disabledConfig)
|
|
|
|
request := &workflowservice.PatchScheduleRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
ScheduleId: "test-schedule",
|
|
Patch: &schedulepb.SchedulePatch{},
|
|
}
|
|
|
|
resp, err := disabledWh.PatchSchedule(ctx, request)
|
|
s.Nil(resp)
|
|
s.Equal(errSchedulesNotAllowed, err)
|
|
})
|
|
|
|
s.Run("request ID too long should return error", func() {
|
|
longRequestID := strings.Repeat("a", common.ScheduleNotesSizeLimit+1)
|
|
request := &workflowservice.PatchScheduleRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
ScheduleId: "test-schedule",
|
|
RequestId: longRequestID,
|
|
Patch: &schedulepb.SchedulePatch{},
|
|
}
|
|
|
|
resp, err := wh.PatchSchedule(ctx, request)
|
|
s.Nil(resp)
|
|
s.Equal(errRequestIDTooLong, err)
|
|
})
|
|
|
|
s.Run("invalid namespace should return error", func() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(namespace.Name("invalid-namespace"))).Return(namespace.ID(""), errors.New("namespace not found")).Times(1)
|
|
|
|
request := &workflowservice.PatchScheduleRequest{
|
|
Namespace: "invalid-namespace",
|
|
ScheduleId: "test-schedule",
|
|
Patch: &schedulepb.SchedulePatch{},
|
|
}
|
|
|
|
resp, err := wh.PatchSchedule(ctx, request)
|
|
s.Nil(resp)
|
|
s.Error(err)
|
|
})
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateTaskQueueConfig_Validation() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
s.Run("rate limit on workflow task queue should return error", func() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).Times(1)
|
|
|
|
request := &workflowservice.UpdateTaskQueueConfigRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
TaskQueue: "test-task-queue",
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
UpdateQueueRateLimit: &workflowservice.UpdateTaskQueueConfigRequest_RateLimitUpdate{
|
|
RateLimit: &taskqueuepb.RateLimit{},
|
|
},
|
|
}
|
|
|
|
resp, err := wh.UpdateTaskQueueConfig(s.T().Context(), request)
|
|
s.Nil(resp)
|
|
s.EqualError(err, "Setting rate limit on workflow task queues is not allowed.")
|
|
})
|
|
|
|
s.Run("fairness key rate limit on workflow task queue should return error", func() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).Times(1)
|
|
|
|
request := &workflowservice.UpdateTaskQueueConfigRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
TaskQueue: "test-task-queue",
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
UpdateFairnessKeyRateLimitDefault: &workflowservice.UpdateTaskQueueConfigRequest_RateLimitUpdate{
|
|
RateLimit: &taskqueuepb.RateLimit{},
|
|
},
|
|
}
|
|
|
|
resp, err := wh.UpdateTaskQueueConfig(s.T().Context(), request)
|
|
s.Nil(resp)
|
|
s.EqualError(err, "Setting fairness key rate limit on workflow task queues is not allowed.")
|
|
})
|
|
|
|
s.Run("fairness weight override on workflow task queue should return success", func() {
|
|
s.mockNamespaceCache.EXPECT().GetNamespaceID(gomock.Eq(s.testNamespace)).Return(s.testNamespaceID, nil).Times(1)
|
|
s.mockMatchingClient.EXPECT().UpdateTaskQueueConfig(gomock.Any(), gomock.Any()).Return(
|
|
&matchingservice.UpdateTaskQueueConfigResponse{
|
|
UpdatedTaskqueueConfig: &taskqueuepb.TaskQueueConfig{},
|
|
}, nil).Times(1)
|
|
|
|
request := &workflowservice.UpdateTaskQueueConfigRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
TaskQueue: "test-task-queue",
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
SetFairnessWeightOverrides: map[string]float32{
|
|
"key1": 1.5,
|
|
"key2": 2.0,
|
|
},
|
|
}
|
|
|
|
resp, err := wh.UpdateTaskQueueConfig(s.T().Context(), request)
|
|
s.NoError(err)
|
|
s.NotNil(resp)
|
|
})
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateWorkflowExecutionOptions_Priority() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
request := &workflowservice.UpdateWorkflowExecutionOptionsRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
RunId: "run-id",
|
|
},
|
|
WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{
|
|
Priority: &commonpb.Priority{PriorityKey: -1},
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"priority"}},
|
|
}
|
|
|
|
_, err := wh.UpdateWorkflowExecutionOptions(context.Background(), request)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "priority key can't be negative")
|
|
// NOTE: only testing a single validation scenario here; the priority validation has its own unit tests
|
|
}
|
|
|
|
// TestUpdateWorkflowExecutionOptions_TimeSkipping_DCDisabled verifies that when the DC gate is
|
|
// off, UpdateWorkflowExecutionOptions rejects a request containing a TimeSkippingConfig.
|
|
func (s *WorkflowHandlerSuite) TestUpdateWorkflowExecutionOptions_TimeSkipping_DCDisabled() {
|
|
config := s.newConfig()
|
|
config.TimeSkippingEnabled = dc.GetBoolPropertyFnFilteredByNamespace(false)
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
_, err := wh.UpdateWorkflowExecutionOptions(context.Background(), &workflowservice.UpdateWorkflowExecutionOptionsRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
RunId: "run-id",
|
|
},
|
|
WorkflowExecutionOptions: &workflowpb.WorkflowExecutionOptions{
|
|
TimeSkippingConfig: &workflowpb.TimeSkippingConfig{Enabled: true},
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"time_skipping_config"}},
|
|
})
|
|
var unimplemented *serviceerror.Unimplemented
|
|
s.ErrorAs(err, &unimplemented)
|
|
s.ErrorContains(err, "The Time-Skipping feature is not enabled for namespace")
|
|
}
|
|
|
|
func (s *WorkflowHandlerSuite) TestUpdateActivityOptions_Priority() {
|
|
config := s.newConfig()
|
|
wh := s.getWorkflowHandler(config)
|
|
|
|
request := &workflowservice.UpdateActivityOptionsRequest{
|
|
Namespace: s.testNamespace.String(),
|
|
Execution: &commonpb.WorkflowExecution{
|
|
WorkflowId: "workflow-id",
|
|
RunId: "run-id",
|
|
},
|
|
Activity: &workflowservice.UpdateActivityOptionsRequest_Id{
|
|
Id: "activity-id",
|
|
},
|
|
ActivityOptions: &activitypb.ActivityOptions{
|
|
Priority: &commonpb.Priority{PriorityKey: -1},
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"priority"}},
|
|
}
|
|
|
|
_, err := wh.UpdateActivityOptions(context.Background(), request)
|
|
var invalidArg *serviceerror.InvalidArgument
|
|
s.ErrorAs(err, &invalidArg)
|
|
s.ErrorContains(err, "priority key can't be negative")
|
|
// NOTE: only testing a single validation scenario here; the priority validation has its own unit tests
|
|
}
|
|
|
|
// routingMatchingClient wraps a mock MatchingServiceClient to also implement matching.RoutingClient,
|
|
// allowing tests to verify host-based deduplication in cancelOutstandingWorkerPolls.
|
|
type routingMatchingClient struct {
|
|
*matchingservicemock.MockMatchingServiceClient
|
|
routeFn func(p tqid.Partition) (string, error)
|
|
}
|
|
|
|
func (r *routingMatchingClient) Route(p tqid.Partition) (string, error) {
|
|
return r.routeFn(p)
|
|
}
|