mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? - WISOTT - Also added a cache per history host so that we don't overburden matching with these calls. - Also added a whole new unit test testing the function `ValidateVersioningOverride` - TODO in a follow-up PR: add metrics for this cache. Doing this as a follow-up in the interest of time but have it tracked in JIRA. ## Why? - Versioning correctness. ## How did you test it? - [ ] built - [ ] run locally and tested manually - [ ] covered by existing tests - [x] added new unit test(s) - [x] added new functional test(s) ## Potential risks - I don't think this is risky --- > [!NOTE] > Validate versioning overrides by ensuring pinned versions exist in the task queue via matching RPC with a per-host cache, refactor validation into history APIs, and add configs and tests. > > - **Worker Versioning / Validation**: > - Add `ValidateVersioningOverride(ctx, ...)` to verify pinned versions exist in a task queue via `matching.CheckTaskQueueVersionMembership`. > - Introduce per-host `versionMembershipCache` to cache membership results; reject with `FailedPrecondition` if not present. > - Remove frontend/batcher inline override validation; perform it in history layer (start, signal-with-start, update options, reset post-ops). > - **History Service Wiring**: > - Thread `matchingClient` and `versionMembershipCache` through history engine, starter, multi-op, signal-with-start, reset, and update-workflow-options APIs. > - Mark batch UpdateWorkflowOptions non-retryable for "Pinned version is not present in the task queue". > - **Config / Dynamic Config**: > - Add `history.versionMembershipCacheTTL` and `history.versionMembershipCacheMaxSize`; provide cache in `fx` with lifecycle management. > - **Testing**: > - Add unit tests for `ValidateVersioningOverride` covering cache hits/misses and v0.31/v0.32 paths. > - Extend functional tests to assert membership checks, cache behavior, batch update failures, and reset with post-reset options. > - Test helpers: `TestVars.WithDeploymentSeries`, `WithBuildID`, and utilities to ensure versions are present via matching RPC. > > <sup>Written by [Cursor Bugbot]() for commit d8b755273fe41c823a30c4cc2ecbbeb2c2be124d. This will update automatically on new commits. Configure [here]().</sup>
229 lines
7.7 KiB
Go
229 lines
7.7 KiB
Go
package tests
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
commonpb "go.temporal.io/api/common/v1"
|
|
deploymentpb "go.temporal.io/api/deployment/v1"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
"go.temporal.io/api/operatorservice/v1"
|
|
taskqueuepb "go.temporal.io/api/taskqueue/v1"
|
|
"go.temporal.io/api/workflowservice/v1"
|
|
"go.temporal.io/sdk/workflow"
|
|
deploymentspb "go.temporal.io/server/api/deployment/v1"
|
|
"go.temporal.io/server/api/matchingservice/v1"
|
|
"go.temporal.io/server/common/payload"
|
|
"go.temporal.io/server/common/searchattribute/sadefs"
|
|
"go.temporal.io/server/common/testing/testvars"
|
|
"go.temporal.io/server/tests/testcore"
|
|
)
|
|
|
|
type WorkflowAliasSearchAttributeTestSuite struct {
|
|
testcore.FunctionalTestBase
|
|
}
|
|
|
|
func TestWorkflowAliasSearchAttributeTestSuite(t *testing.T) {
|
|
s := new(WorkflowAliasSearchAttributeTestSuite)
|
|
suite.Run(t, s)
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) SetupTest() {
|
|
s.FunctionalTestBase.SetupTest()
|
|
|
|
s.Worker().RegisterWorkflow(s.workflowFunc)
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) workflowFunc(ctx workflow.Context) (string, error) {
|
|
return "done!", nil
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) startVersionedPollerAndValidate(
|
|
ctx context.Context,
|
|
taskQueueName string,
|
|
deploymentName string,
|
|
buildID string,
|
|
) {
|
|
taskQueue := &taskqueuepb.TaskQueue{
|
|
Name: taskQueueName,
|
|
Kind: enumspb.TASK_QUEUE_KIND_NORMAL,
|
|
}
|
|
|
|
// Start versioned poller in background
|
|
go func() {
|
|
_, _ = s.FrontendClient().PollWorkflowTaskQueue(ctx, &workflowservice.PollWorkflowTaskQueueRequest{
|
|
Namespace: s.Namespace().String(),
|
|
TaskQueue: taskQueue,
|
|
Identity: "versioned-poller",
|
|
DeploymentOptions: &deploymentpb.WorkerDeploymentOptions{
|
|
DeploymentName: deploymentName,
|
|
BuildId: buildID,
|
|
WorkerVersioningMode: enumspb.WORKER_VERSIONING_MODE_VERSIONED,
|
|
},
|
|
})
|
|
}()
|
|
|
|
// Validate version is present via matching RPC
|
|
version := &deploymentspb.WorkerDeploymentVersion{
|
|
DeploymentName: deploymentName,
|
|
BuildId: buildID,
|
|
}
|
|
s.EventuallyWithT(func(t *assert.CollectT) {
|
|
a := require.New(t)
|
|
resp, err := s.GetTestCluster().MatchingClient().CheckTaskQueueVersionMembership(
|
|
ctx,
|
|
&matchingservice.CheckTaskQueueVersionMembershipRequest{
|
|
NamespaceId: s.NamespaceID().String(),
|
|
TaskQueue: taskQueueName,
|
|
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
|
|
Version: version,
|
|
},
|
|
)
|
|
a.NoError(err)
|
|
a.True(resp.GetIsMember())
|
|
}, 10*time.Second, 1*time.Second)
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) createWorkflow(
|
|
ctx context.Context,
|
|
tv *testvars.TestVars,
|
|
sa *commonpb.SearchAttributes,
|
|
) (*workflowservice.StartWorkflowExecutionResponse, error) {
|
|
// Start a versioned poller so that the version, which will be set as an override, is present in the task queue.
|
|
s.startVersionedPollerAndValidate(ctx, tv.TaskQueue().Name, tv.DeploymentSeries(), tv.BuildID())
|
|
|
|
request := &workflowservice.StartWorkflowExecutionRequest{
|
|
RequestId: tv.Any().String(),
|
|
Namespace: s.Namespace().String(),
|
|
WorkflowId: tv.WorkflowID(),
|
|
WorkflowType: tv.WorkflowType(),
|
|
TaskQueue: tv.TaskQueue(),
|
|
Identity: tv.WorkerIdentity(),
|
|
VersioningOverride: tv.VersioningOverridePinned(true),
|
|
SearchAttributes: sa,
|
|
}
|
|
return s.FrontendClient().StartWorkflowExecution(ctx, request)
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) terminateWorkflow(
|
|
ctx context.Context,
|
|
tv *testvars.TestVars,
|
|
) (*workflowservice.TerminateWorkflowExecutionResponse, error) {
|
|
return s.FrontendClient().TerminateWorkflowExecution(ctx, &workflowservice.TerminateWorkflowExecutionRequest{
|
|
Namespace: s.Namespace().String(),
|
|
WorkflowExecution: &commonpb.WorkflowExecution{
|
|
WorkflowId: tv.WorkflowID(),
|
|
},
|
|
Reason: "terminate reason",
|
|
})
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) TestWorkflowAliasSearchAttribute() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Overriding the task queue name, deployment name, and build ID so that we don't hit length limitations
|
|
// for any of these in any of the databases.
|
|
tv := testvars.New(s.T()).
|
|
WithTaskQueue("tq-alias-sa").
|
|
WithDeploymentSeries("dep-alias-sa").
|
|
WithBuildID("build-alias-sa")
|
|
|
|
_, err := s.createWorkflow(ctx, tv, nil)
|
|
s.Require().NoError(err)
|
|
|
|
s.EventuallyWithT(
|
|
func(t *assert.CollectT) {
|
|
// Filter by WorkflowId to isolate this test's workflow from other tests
|
|
resp, err := s.SdkClient().ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
|
Namespace: s.Namespace().String(),
|
|
Query: fmt.Sprintf("WorkflowId = '%s'", tv.WorkflowID()),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Len(t, resp.GetExecutions(), 1)
|
|
|
|
queriedResp, err := s.SdkClient().ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
|
Namespace: s.Namespace().String(),
|
|
Query: fmt.Sprintf("%s = 'Pinned' AND WorkflowId = '%s'", sadefs.TemporalWorkflowVersioningBehavior, tv.WorkflowID()),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Len(t, queriedResp.GetExecutions(), 1)
|
|
|
|
queriedResp, err = s.SdkClient().ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
|
Namespace: s.Namespace().String(),
|
|
Query: fmt.Sprintf("WorkflowVersioningBehavior = 'Pinned' AND WorkflowId = '%s'", tv.WorkflowID()),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Len(t, queriedResp.GetExecutions(), 1)
|
|
},
|
|
testcore.WaitForESToSettle,
|
|
100*time.Millisecond,
|
|
)
|
|
|
|
_, err = s.terminateWorkflow(ctx, tv)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *WorkflowAliasSearchAttributeTestSuite) TestWorkflowAliasSearchAttribute_CustomSearchAttributeOverride() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Overriding the task queue name, deployment name, and build ID so that we don't hit length limitations
|
|
// for any of these in any of the databases.
|
|
tv := testvars.New(s.T()).
|
|
WithTaskQueue("tq-alias-sa-custom").
|
|
WithDeploymentSeries("dep-alias-sa-custom").
|
|
WithBuildID("build-alias-sa-custom")
|
|
|
|
_, err := s.SdkClient().OperatorService().AddSearchAttributes(ctx, &operatorservice.AddSearchAttributesRequest{
|
|
Namespace: s.Namespace().String(),
|
|
SearchAttributes: map[string]enumspb.IndexedValueType{
|
|
"WorkflowVersioningBehavior": enumspb.INDEXED_VALUE_TYPE_KEYWORD,
|
|
},
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
sa := &commonpb.SearchAttributes{
|
|
IndexedFields: map[string]*commonpb.Payload{
|
|
"WorkflowVersioningBehavior": payload.EncodeString("user-defined"),
|
|
},
|
|
}
|
|
|
|
_, err = s.createWorkflow(ctx, tv, sa)
|
|
s.Require().NoError(err)
|
|
|
|
s.EventuallyWithT(
|
|
func(t *assert.CollectT) {
|
|
// Filter by WorkflowId to isolate this test's workflow from other tests
|
|
queriedResp, err := s.SdkClient().ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
|
Namespace: s.Namespace().String(),
|
|
Query: fmt.Sprintf("%s = 'Pinned' AND WorkflowId = '%s'", sadefs.TemporalWorkflowVersioningBehavior, tv.WorkflowID()),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, queriedResp)
|
|
require.Len(t, queriedResp.GetExecutions(), 1)
|
|
|
|
queriedResp, err = s.SdkClient().ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
|
Namespace: s.Namespace().String(),
|
|
Query: fmt.Sprintf("WorkflowVersioningBehavior = 'user-defined' AND WorkflowId = '%s'", tv.WorkflowID()),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, queriedResp)
|
|
require.Len(t, queriedResp.GetExecutions(), 1)
|
|
},
|
|
testcore.WaitForESToSettle,
|
|
100*time.Millisecond,
|
|
)
|
|
|
|
_, err = s.terminateWorkflow(ctx, tv)
|
|
s.Require().NoError(err)
|
|
}
|