Files
tasks/tests/worker_deployment_version_test.go
ShivamandStephan Behnke 445eacfff9 Non-Retryable error returned when deleting current/ramping version (#7399)
## What changed?
<!-- Describe what has changed in this PR -->
- Returning NonRetryable errors when version to be deleted is
current/ramping.

## Why?
<!-- Tell your future self why have you made these changes -->
- To not lock workflow state.

## How did you test it?
<!-- How have you verified this change? Tested locally? Added a unit
test? Checked in staging env? -->
- Modified existing tests by reducing the context timeout value and
noticing things pass. Moreover, added another operation after a delete
request such as the one mentioned above to verify workflow is not in a
locked state.

## Potential risks
<!-- Assuming the worst case, what can be broken when deploying this
change to production? -->

## Documentation
<!-- Have you made sure this change doesn't falsify anything currently
stated in `docs/`? If significant
new behavior is added, have you described that in `docs/`? -->

## Is hotfix candidate?
<!-- Is this PR a hotfix candidate or does it require a notification to
be sent to the broader community? (Yes/No) -->
2025-03-25 15:43:26 -07:00

1295 lines
48 KiB
Go

// The MIT License
//
// Copyright (c) 2024 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package tests
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"testing"
"time"
"github.com/dgryski/go-farm"
"github.com/stretchr/testify/assert"
"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/serviceerror"
workflowpb "go.temporal.io/api/workflow/v1"
"go.temporal.io/api/workflowservice/v1"
sdkclient "go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
"go.temporal.io/sdk/workflow"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/testing/testvars"
"go.temporal.io/server/common/tqid"
"go.temporal.io/server/common/worker_versioning"
"go.temporal.io/server/service/worker/workerdeployment"
"go.temporal.io/server/tests/testcore"
"google.golang.org/protobuf/types/known/timestamppb"
)
const (
maxConcurrentBatchOperations = 3
testVersionDrainageRefreshInterval = 3 * time.Second
testVersionDrainageVisibilityGracePeriod = 3 * time.Second
testMaxVersionsInDeployment = 5
)
type (
DeploymentVersionSuite struct {
testcore.FunctionalTestSuite
sdkClient sdkclient.Client
}
)
var (
testRandomMetadataValue = []byte("random metadata value")
)
func TestDeploymentVersionSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(DeploymentVersionSuite))
}
func (s *DeploymentVersionSuite) SetupSuite() {
s.FunctionalTestSuite.SetupSuiteWithDefaultCluster(testcore.WithDynamicConfigOverrides(map[dynamicconfig.Key]any{
dynamicconfig.EnableDeploymentVersions.Key(): true,
dynamicconfig.FrontendEnableWorkerVersioningDataAPIs.Key(): true, // [wv-cleanup-pre-release]
dynamicconfig.FrontendEnableWorkerVersioningWorkflowAPIs.Key(): true, // [wv-cleanup-pre-release]
dynamicconfig.FrontendEnableWorkerVersioningRuleAPIs.Key(): true, // [wv-cleanup-pre-release]
dynamicconfig.FrontendEnableExecuteMultiOperation.Key(): true,
// Make sure we don't hit the rate limiter in tests
dynamicconfig.FrontendGlobalNamespaceNamespaceReplicationInducingAPIsRPS.Key(): 1000,
dynamicconfig.FrontendMaxNamespaceNamespaceReplicationInducingAPIsBurstRatioPerInstance.Key(): 1,
dynamicconfig.FrontendNamespaceReplicationInducingAPIsRPS.Key(): 1000,
// Reduce the chance of hitting max batch job limit in tests
dynamicconfig.FrontendMaxConcurrentBatchOperationPerNamespace.Key(): maxConcurrentBatchOperations,
dynamicconfig.VersionDrainageStatusRefreshInterval.Key(): testVersionDrainageRefreshInterval,
dynamicconfig.VersionDrainageStatusVisibilityGracePeriod.Key(): testVersionDrainageVisibilityGracePeriod,
}))
}
func (s *DeploymentVersionSuite) SetupTest() {
s.FunctionalTestSuite.SetupTest()
var err error
s.sdkClient, err = sdkclient.Dial(sdkclient.Options{
HostPort: s.FrontendGRPCAddress(),
Namespace: s.Namespace().String(),
})
s.NoError(err)
}
func (s *DeploymentVersionSuite) TearDownTest() {
if s.sdkClient != nil {
s.sdkClient.Close()
}
}
// pollFromDeployment calls PollWorkflowTaskQueue to start deployment related workflows
func (s *DeploymentVersionSuite) pollFromDeployment(ctx context.Context, tv *testvars.TestVars) {
_, _ = s.FrontendClient().PollWorkflowTaskQueue(ctx, &workflowservice.PollWorkflowTaskQueueRequest{
Namespace: s.Namespace().String(),
TaskQueue: tv.TaskQueue(),
Identity: "random",
DeploymentOptions: tv.WorkerDeploymentOptions(true),
})
}
func (s *DeploymentVersionSuite) pollActivityFromDeployment(ctx context.Context, tv *testvars.TestVars) {
_, _ = s.FrontendClient().PollActivityTaskQueue(ctx, &workflowservice.PollActivityTaskQueueRequest{
Namespace: s.Namespace().String(),
TaskQueue: tv.TaskQueue(),
Identity: "random",
DeploymentOptions: tv.WorkerDeploymentOptions(true),
})
}
func (s *DeploymentVersionSuite) startVersionWorkflow(ctx context.Context, tv *testvars.TestVars) {
go s.pollFromDeployment(ctx, tv)
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
a.NoError(err)
a.Equal(tv.DeploymentVersionString(), resp.GetWorkerDeploymentVersionInfo().GetVersion())
newResp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv.DeploymentSeries(),
})
a.NoError(err)
var versionSummaryNames []string
for _, versionSummary := range newResp.GetWorkerDeploymentInfo().GetVersionSummaries() {
versionSummaryNames = append(versionSummaryNames, versionSummary.GetVersion())
}
a.Contains(versionSummaryNames, tv.DeploymentVersionString())
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) startVersionWorkflowExpectFailAddVersion(ctx context.Context, tv *testvars.TestVars) {
go s.pollFromDeployment(ctx, tv)
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
newResp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv.DeploymentSeries(),
})
a.NoError(err)
var versionSummaryNames []string
for _, versionSummary := range newResp.GetWorkerDeploymentInfo().GetVersionSummaries() {
versionSummaryNames = append(versionSummaryNames, versionSummary.GetVersion())
}
a.NotContains(versionSummaryNames, tv.DeploymentVersionString())
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestForceCAN_NoOpenWFS() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
tv := testvars.New(s)
// Start a version workflow
s.startVersionWorkflow(ctx, tv)
// Set the version as current
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv.DeploymentSeries(),
Version: tv.DeploymentVersionString(),
})
s.NoError(err)
// ForceCAN
versionWorkflowID := worker_versioning.GenerateVersionWorkflowID(tv.DeploymentSeries(), tv.BuildID())
workflowExecution := &commonpb.WorkflowExecution{
WorkflowId: versionWorkflowID,
}
err = s.SendSignal(s.Namespace().String(), workflowExecution, workerdeployment.ForceCANSignalName, nil, tv.ClientIdentity())
s.NoError(err)
// verifying we see our registered workers in the version deployment even after a CAN
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
if !a.NoError(err) {
return
}
a.Equal(tv.DeploymentVersionString(), resp.GetWorkerDeploymentVersionInfo().GetVersion())
if !a.Equal(1, len(resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos())) {
return
}
// verify that the version state is intact even after a CAN
a.Equal(tv.TaskQueue().GetName(), resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos()[0].Name)
a.NotNil(resp.GetWorkerDeploymentVersionInfo().GetCurrentSinceTime())
a.NotNil(resp.GetWorkerDeploymentVersionInfo().GetRoutingChangedTime())
a.NotNil(resp.GetWorkerDeploymentVersionInfo().GetCurrentSinceTime())
a.Nil(resp.GetWorkerDeploymentVersionInfo().GetDrainageInfo())
}, time.Second*10, time.Millisecond*1000)
}
func (s *DeploymentVersionSuite) TestDescribeVersion_RegisterTaskQueue() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
tv := testvars.New(s)
numberOfDeployments := 1
// Starting a deployment workflow
go s.pollFromDeployment(ctx, tv)
// Querying the Deployment
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
a.NoError(err)
a.Equal(tv.DeploymentVersionString(), resp.GetWorkerDeploymentVersionInfo().GetVersion())
a.Equal(numberOfDeployments, len(resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos()))
if len(resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos()) < numberOfDeployments {
return
}
a.Equal(tv.TaskQueue().GetName(), resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos()[0].Name)
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestDescribeVersion_RegisterTaskQueue_ConcurrentPollers() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
tv := testvars.New(s)
root, err := tqid.PartitionFromProto(tv.TaskQueue(), s.Namespace().String(), enumspb.TASK_QUEUE_TYPE_WORKFLOW)
s.NoError(err)
// Making concurrent polls to 4 partitions, 3 polls to each
for p := 0; p < 4; p++ {
tv2 := tv.WithTaskQueue(root.TaskQueue().NormalPartition(p).RpcName())
for i := 0; i < 3; i++ {
go s.pollFromDeployment(ctx, tv2)
go s.pollActivityFromDeployment(ctx, tv2)
}
}
// Querying the Deployment
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
if !a.NoError(err) {
return
}
a.Equal(tv.DeploymentVersionString(), resp.GetWorkerDeploymentVersionInfo().GetVersion())
if !a.Equal(2, len(resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos())) {
return
}
a.Equal(tv.TaskQueue().GetName(), resp.GetWorkerDeploymentVersionInfo().GetTaskQueueInfos()[0].Name)
}, time.Second*10, time.Millisecond*1000)
}
// Name is used by testvars. We use a shorten test name in variables so that physical task queue IDs
// do not grow larger that DB column limit (currently as low as 272 chars).
func (s *DeploymentVersionSuite) Name() string {
fullName := s.T().Name()
if len(fullName) <= 30 {
return fullName
}
short := fmt.Sprintf("%s-%08x",
fullName[len(fullName)-21:],
farm.Fingerprint32([]byte(fullName)),
)
return strings.Replace(short, ".", "|", -1)
}
//nolint:forbidigo
func (s *DeploymentVersionSuite) TestDrainageStatus_SetCurrentVersion_NoOpenWFs() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
tv2 := testvars.New(s).WithBuildIDNumber(2)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Start deployment workflow 2 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv2)
// non-current deployments have never been used and have no drainage info
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{}, false, false)
s.checkVersionDrainage(ctx, tv2, &deploymentpb.VersionDrainageInfo{}, false, false)
// SetCurrent tv1
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
Identity: tv1.ClientIdentity(),
IgnoreMissingTaskQueues: true,
})
s.Nil(err)
// both still nil since neither are draining
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{}, false, false)
s.checkVersionDrainage(ctx, tv2, &deploymentpb.VersionDrainageInfo{}, false, false)
// SetCurrent tv2 --> tv1 starts the child drainage workflow
_, err = s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: true,
})
s.Nil(err)
// tv1 should now be "draining" for visibilityGracePeriod duration
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINING,
LastChangedTime: nil, // don't test this now
LastCheckedTime: nil, // don't test this now
}, false, false)
// tv1 should now be "drained"
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINED,
LastChangedTime: nil, // don't test this now
LastCheckedTime: nil, // don't test this now
}, true, false)
}
func (s *DeploymentVersionSuite) TestDrainageStatus_SetCurrentVersion_YesOpenWFs() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
tv2 := testvars.New(s).WithBuildIDNumber(2)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Start deployment workflow 2 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv2)
// non-current deployments have never been used and have no drainage info
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{}, false, false)
s.checkVersionDrainage(ctx, tv2, &deploymentpb.VersionDrainageInfo{}, false, false)
// SetCurrent tv1
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
Identity: tv1.ClientIdentity(),
IgnoreMissingTaskQueues: true,
})
s.Nil(err)
// both still nil since neither are draining
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{}, false, false)
s.checkVersionDrainage(ctx, tv2, &deploymentpb.VersionDrainageInfo{}, false, false)
// start a pinned workflow on v1
run := s.startPinnedWorkflow(ctx, tv1)
// SetCurrent tv2 --> tv1 starts the child drainage workflow
_, err = s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: true,
})
s.Nil(err)
// tv1 should now be "draining" for visibilityGracePeriod duration
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINING,
LastChangedTime: nil, // don't test this now
LastCheckedTime: nil, // don't test this now
}, false, false)
// tv1 should still be "draining" for visibilityGracePeriod duration
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINING,
LastChangedTime: nil, // don't test this now
LastCheckedTime: nil, // don't test this now
}, true, false)
// terminate workflow
_, err = s.FrontendClient().TerminateWorkflowExecution(ctx, &workflowservice.TerminateWorkflowExecutionRequest{
Namespace: s.Namespace().String(),
WorkflowExecution: &commonpb.WorkflowExecution{
WorkflowId: run.GetID(),
RunId: run.GetRunID(),
},
Reason: "test",
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// tv1 should now be "drained"
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINED,
LastChangedTime: nil, // don't test this now
LastCheckedTime: nil, // don't test this now
}, false, true)
}
func (s *DeploymentVersionSuite) startPinnedWorkflow(ctx context.Context, tv *testvars.TestVars) sdkclient.WorkflowRun {
started := make(chan struct{}, 1)
wf := func(ctx workflow.Context) (string, error) {
started <- struct{}{}
workflow.GetSignalChannel(ctx, "wait").Receive(ctx, nil)
if workflow.GetInfo(ctx).Attempt == 1 {
return "", errors.New("try again") //nolint:err113
}
panic("oops")
}
wId := testcore.RandomizeStr("id")
w := worker.New(s.sdkClient, tv.TaskQueue().String(), worker.Options{
DeploymentOptions: worker.DeploymentOptions{
DeploymentSeriesName: tv.DeploymentSeries(),
},
UseBuildIDForVersioning: true,
BuildID: tv.BuildID(),
Identity: wId,
})
w.RegisterWorkflowWithOptions(wf, workflow.RegisterOptions{VersioningBehavior: workflow.VersioningBehaviorPinned})
s.NoError(w.Start())
defer w.Stop()
run, err := s.sdkClient.ExecuteWorkflow(ctx, sdkclient.StartWorkflowOptions{TaskQueue: tv.TaskQueue().String()}, wf)
s.NoError(err)
s.WaitForChannel(ctx, started)
return run
}
func (s *DeploymentVersionSuite) TestVersionIgnoresDrainageSignalWhenCurrentOrRamping() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Make it current
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// Signal it to be drained. Only do this in tests.
versionWorkflowID := worker_versioning.GenerateVersionWorkflowID(tv1.DeploymentSeries(), tv1.BuildID())
workflowExecution := &commonpb.WorkflowExecution{
WorkflowId: versionWorkflowID,
}
input := &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINED,
LastChangedTime: timestamppb.New(time.Now()),
LastCheckedTime: timestamppb.New(time.Now()),
}
marshaledData, err := input.Marshal()
s.NoError(err)
signalPayload := &commonpb.Payloads{
Payloads: []*commonpb.Payload{
{
Metadata: map[string][]byte{
"encoding": []byte("binary/protobuf"),
},
Data: marshaledData,
},
},
}
err = s.SendSignal(s.Namespace().String(), workflowExecution, workerdeployment.SyncDrainageSignalName, signalPayload, tv1.ClientIdentity())
s.Nil(err)
// describe version and confirm that it is not drained
// add a 3s time requirement so that it does not succeed immediately
sentSignal := time.Now()
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
a.Greater(time.Since(sentSignal), 2*time.Second)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv1.DeploymentVersionString(),
})
a.NoError(err)
a.NotEqual(enumspb.VERSION_DRAINAGE_STATUS_DRAINED, resp.GetWorkerDeploymentVersionInfo().GetDrainageInfo().GetStatus())
}, time.Second*10, time.Millisecond*1000)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_DeleteCurrentVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Create a deployment version
s.startVersionWorkflow(ctx, tv1)
// Set version as current
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// Deleting this version should fail since the version is current
s.tryDeleteVersion(ctx, tv1, false, false)
// Verifying workflow is not in a locked state after an invalid delete request such as the one above. If the workflow were in a locked
// state, the passed context would have timed out making the following operation fail.
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
})
a.NoError(err)
a.Equal(tv1.DeploymentVersionString(), resp.GetWorkerDeploymentInfo().GetRoutingConfig().GetCurrentVersion())
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_DeleteRampedVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Create a deployment version
s.startVersionWorkflow(ctx, tv1)
// Set version as ramping
_, err := s.FrontendClient().SetWorkerDeploymentRampingVersion(ctx, &workflowservice.SetWorkerDeploymentRampingVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// Deleting this version should fail since the version is ramping
s.tryDeleteVersion(ctx, tv1, false, false)
// Verifying workflow is not in a locked state after an invalid delete request such as the one above. If the workflow were in a locked
// state, the passed context would have timed out making the following operation fail.
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
})
a.NoError(err)
a.Equal(tv1.DeploymentVersionString(), resp.GetWorkerDeploymentInfo().GetRoutingConfig().GetRampingVersion())
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_NoWfs() {
s.T().Skip("skipping this test for now until I make TTL of pollerHistoryTTL configurable by dynamic config.")
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Create a deployment version
s.startVersionWorkflow(ctx, tv1)
//nolint:forbidigo
time.Sleep(2 * time.Second) // todo (Shivam): remove this after the above skip is removed
// delete should succeed
s.tryDeleteVersion(ctx, tv1, true, false)
// deployment version does not exist in the deployment list
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
})
a.NoError(err)
if resp != nil {
for _, vs := range resp.GetWorkerDeploymentInfo().GetVersionSummaries() {
a.NotEqual(tv1.DeploymentVersionString(), vs.Version)
}
}
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_DrainingVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Make the version current
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// Start another version workflow
tv2 := testvars.New(s).WithBuildIDNumber(2)
s.startVersionWorkflow(ctx, tv2)
// Setting this version to current should start the drainage workflow for version1 and make it draining
_, err = s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: true,
})
s.Nil(err)
// Version should be draining
s.checkVersionDrainage(ctx, tv1, &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINING,
LastChangedTime: nil, // don't test this now
LastCheckedTime: nil, // don't test this now
}, false, false)
// delete should fail
s.tryDeleteVersion(ctx, tv1, false, false)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_Drained_But_Pollers_Exist() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Make the version current
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// Start another version workflow
tv2 := testvars.New(s).WithBuildIDNumber(2)
s.startVersionWorkflow(ctx, tv2)
// Setting this version to current should start the drainage workflow for version1
_, err = s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: true,
})
s.Nil(err)
// Signal the first version to be drained. Only do this in tests.
versionWorkflowID := worker_versioning.GenerateVersionWorkflowID(tv1.DeploymentSeries(), tv1.BuildID())
workflowExecution := &commonpb.WorkflowExecution{
WorkflowId: versionWorkflowID,
}
input := &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINED,
LastChangedTime: timestamppb.New(time.Now()),
LastCheckedTime: timestamppb.New(time.Now()),
}
marshaledData, err := input.Marshal()
s.NoError(err)
signalPayload := &commonpb.Payloads{
Payloads: []*commonpb.Payload{
{
Metadata: map[string][]byte{
"encoding": []byte("binary/protobuf"),
},
Data: marshaledData,
},
},
}
err = s.SendSignal(s.Namespace().String(), workflowExecution, workerdeployment.SyncDrainageSignalName, signalPayload, tv1.ClientIdentity())
s.Nil(err)
// Version will bypass "drained" check but delete should still fail since we have active pollers.
s.tryDeleteVersion(ctx, tv1, false, false)
}
func (s *DeploymentVersionSuite) signalAndWaitForDrained(ctx context.Context, tv *testvars.TestVars) {
versionWorkflowID := worker_versioning.GenerateVersionWorkflowID(tv.DeploymentSeries(), tv.BuildID())
workflowExecution := &commonpb.WorkflowExecution{
WorkflowId: versionWorkflowID,
}
input := &deploymentpb.VersionDrainageInfo{
Status: enumspb.VERSION_DRAINAGE_STATUS_DRAINED,
LastChangedTime: timestamppb.New(time.Now()),
LastCheckedTime: timestamppb.New(time.Now()),
}
marshaledData, err := input.Marshal()
s.NoError(err)
signalPayload := &commonpb.Payloads{
Payloads: []*commonpb.Payload{
{
Metadata: map[string][]byte{
"encoding": []byte("binary/protobuf"),
},
Data: marshaledData,
},
},
}
err = s.SendSignal(s.Namespace().String(), workflowExecution, workerdeployment.SyncDrainageSignalName, signalPayload, tv.ClientIdentity())
s.Nil(err)
// wait for drained
s.EventuallyWithT(func(t *assert.CollectT) {
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
assert.NoError(t, err)
assert.Equal(t, enumspb.VERSION_DRAINAGE_STATUS_DRAINED, resp.GetWorkerDeploymentVersionInfo().GetDrainageInfo().GetStatus())
}, 10*time.Second, time.Second)
}
func (s *DeploymentVersionSuite) waitForNoPollers(ctx context.Context, tv *testvars.TestVars) {
s.EventuallyWithT(func(t *assert.CollectT) {
resp, err := s.FrontendClient().DescribeTaskQueue(ctx, &workflowservice.DescribeTaskQueueRequest{
Namespace: s.Namespace().String(),
TaskQueue: tv.TaskQueue(),
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
})
assert.NoError(t, err)
assert.Empty(t, resp.Pollers)
}, 10*time.Second, time.Second)
}
func (s *DeploymentVersionSuite) TestVersionScavenger_DeleteOnAdd() {
s.T().Skip("skipping this test for now until I make TTL of pollerHistoryTTL configurable by dynamic config.")
// TODO: This test also requires hardcoding maxVersions in the deployment workflow until dynamic config is settable
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
tvs := make([]*testvars.TestVars, testMaxVersionsInDeployment)
// max out the versions
for i := 0; i < testMaxVersionsInDeployment; i++ {
tvs[i] = testvars.New(s).WithBuildIDNumber(i).WithTaskQueue(fmt.Sprintf("%d", i))
s.startVersionWorkflow(ctx, tvs[i])
}
tvMax := testvars.New(s).WithBuildIDNumber(9999)
// try to add a version and it fails
s.startVersionWorkflowExpectFailAddVersion(ctx, tvMax)
// signal the second and third wfs to be drained (testing that we don't delete the first version just due to create time oldest)
s.signalAndWaitForDrained(ctx, tvs[1])
s.signalAndWaitForDrained(ctx, tvs[2])
// Wait for pollers going away
s.waitForNoPollers(ctx, tvs[1])
s.waitForNoPollers(ctx, tvs[2])
// try to add a version again, and it succeeds, after deleting the second version but not the third (both are eligible)
// TODO: This fails if I try to add tvMax again...
s.startVersionWorkflow(ctx, testvars.New(s).WithBuildIDNumber(1111))
// second deployment version does not exist in the deployment list, third version does
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tvMax.DeploymentSeries(),
})
a.NoError(err)
var versions []string
for _, vs := range resp.GetWorkerDeploymentInfo().GetVersionSummaries() {
versions = append(versions, vs.Version)
}
a.NotContains(versions, tvs[1].DeploymentVersionString())
a.Contains(versions, tvs[2].DeploymentVersionString())
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_ValidDelete() {
s.OverrideDynamicConfig(dynamicconfig.PollerHistoryTTL, 500*time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Signal the first version to be drained. Only do this in tests.
s.signalAndWaitForDrained(ctx, tv1)
// Wait for pollers going away
s.waitForNoPollers(ctx, tv1)
// delete succeeds
s.tryDeleteVersion(ctx, tv1, true, false)
// deployment version does not exist in the deployment list
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
})
a.NoError(err)
if resp != nil {
for _, vs := range resp.GetWorkerDeploymentInfo().GetVersionSummaries() {
a.NotEqual(tv1.DeploymentVersionString(), vs.Version)
}
}
}, time.Second*5, time.Millisecond*200)
// idempotency check: deleting the same version again should succeed
s.tryDeleteVersion(ctx, tv1, true, false)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_ValidDelete_SkipDrainage() {
s.OverrideDynamicConfig(dynamicconfig.PollerHistoryTTL, 500*time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Wait for pollers going away
s.EventuallyWithT(func(t *assert.CollectT) {
resp, err := s.FrontendClient().DescribeTaskQueue(ctx, &workflowservice.DescribeTaskQueueRequest{
Namespace: s.Namespace().String(),
TaskQueue: tv1.TaskQueue(),
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
})
assert.NoError(t, err)
assert.Empty(t, resp.Pollers)
}, 5*time.Second, time.Second)
// skipDrainage=true will make delete succeed
s.tryDeleteVersion(ctx, tv1, true, true)
// deployment version does not exist in the deployment list
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
})
a.NoError(err)
if resp != nil {
for _, vs := range resp.GetWorkerDeploymentInfo().GetVersionSummaries() {
a.NotEqual(tv1.DeploymentVersionString(), vs.Version)
}
}
}, time.Second*5, time.Millisecond*200)
// idempotency check: deleting the same version again should succeed
s.tryDeleteVersion(ctx, tv1, true, true)
// Describe Worker Deployment should give not found
// describe deployment version gives not found error
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
_, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv1.DeploymentVersionString(),
})
a.Error(err)
var nfe *serviceerror.NotFound
a.True(errors.As(err, &nfe))
}, time.Second*5, time.Millisecond*200)
}
func (s *DeploymentVersionSuite) TestDeleteVersion_ConcurrentDeleteVersion() {
s.OverrideDynamicConfig(dynamicconfig.PollerHistoryTTL, 500*time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// Wait for pollers going away
s.EventuallyWithT(func(t *assert.CollectT) {
resp, err := s.FrontendClient().DescribeTaskQueue(ctx, &workflowservice.DescribeTaskQueueRequest{
Namespace: s.Namespace().String(),
TaskQueue: tv1.TaskQueue(),
TaskQueueType: enumspb.TASK_QUEUE_TYPE_WORKFLOW,
})
assert.NoError(t, err)
assert.Empty(t, resp.Pollers)
}, 10*time.Second, time.Second)
// concurrent delete version requests should not break the system.
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
s.tryDeleteVersion(ctx, tv1, true, true)
}()
go func() {
defer wg.Done()
s.tryDeleteVersion(ctx, tv1, true, true)
}()
wg.Wait()
// deployment version does not exist in the deployment list
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeployment(ctx, &workflowservice.DescribeWorkerDeploymentRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
})
a.NoError(err)
if resp != nil {
for _, vs := range resp.GetWorkerDeploymentInfo().GetVersionSummaries() {
a.NotEqual(tv1.DeploymentVersionString(), vs.Version)
}
}
}, time.Second*5, time.Millisecond*200)
}
// VersionMissingTaskQueues
func (s *DeploymentVersionSuite) TestVersionMissingTaskQueues_InvalidSetCurrentVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv := testvars.New(s)
tv1 := tv.WithBuildIDNumber(1).WithTaskQueue(tv.Any().String())
// Start deployment workflow 1 and wait for the deployment version to exist
pollerCtx1, pollerCancel1 := context.WithCancel(ctx)
s.startVersionWorkflow(pollerCtx1, tv1)
// SetCurrent so that the task queue puts the version in its versions info
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// new version with a different registered task-queue
tv2 := testvars.New(s).WithBuildIDNumber(2).WithTaskQueue(testvars.New(s.T()).Any().String())
s.startVersionWorkflow(ctx, tv2)
// Cancel pollers on task_queue_1 to increase the backlog of tasks
pollerCancel1()
// Start a workflow on task_queue_1 to increase the add rate
s.startWorkflow(tv1, tv1.VersioningOverridePinned())
// SetCurrent tv2
_, err = s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: false,
})
// SetCurrent should fail since task_queue_1 does not have a current version than the deployment's existing current version
// and it either has a backlog of tasks being present or an add rate > 0.
s.Error(err)
}
func (s *DeploymentVersionSuite) TestVersionMissingTaskQueues_ValidSetCurrentVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv := testvars.New(s)
tv1 := tv.WithBuildIDNumber(1).WithTaskQueue(tv.Any().String())
s.startVersionWorkflow(ctx, tv1)
// SetCurrent so that the task queue puts the version in its versions info
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// new version with a different registered task-queue
tv2 := tv.WithBuildIDNumber(2).WithTaskQueue(tv.Any().String())
s.startVersionWorkflow(ctx, tv2)
// SetCurrent tv2
_, err = s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: false,
})
// SetCurrent tv2 should succeed as task_queue_1, despite missing from the new current version, has no backlogged tasks/add-rate > 0
s.Nil(err)
}
func (s *DeploymentVersionSuite) TestVersionMissingTaskQueues_InvalidSetRampingVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv := testvars.New(s)
tv1 := tv.WithBuildIDNumber(1).WithTaskQueue(tv.Any().String())
// Start deployment workflow 1 and wait for the deployment version to exist
pollerCtx1, pollerCancel1 := context.WithCancel(ctx)
s.startVersionWorkflow(pollerCtx1, tv1)
// SetCurrent so that the task queue puts the version in its versions info
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// new version with a different registered task-queue
tv2 := tv.WithBuildIDNumber(2).WithTaskQueue(tv.Any().String())
s.startVersionWorkflow(ctx, tv2)
// Cancel pollers on task_queue_1 to increase the backlog of tasks
pollerCancel1()
// Start a workflow on task_queue_1 to increase the add rate
s.startWorkflow(tv1, tv1.VersioningOverridePinned())
// SetRampingVersion to tv2
_, err = s.FrontendClient().SetWorkerDeploymentRampingVersion(ctx, &workflowservice.SetWorkerDeploymentRampingVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: false,
})
// SetRampingVersion should fail since task_queue_1 does not have a current version than the deployment's existing current version
// and it either has a backlog of tasks being present or an add rate > 0.
s.Error(err)
}
func (s *DeploymentVersionSuite) TestVersionMissingTaskQueues_ValidSetRampingVersion() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv := testvars.New(s)
tv1 := tv.WithBuildIDNumber(1).WithTaskQueue(tv.Any().String())
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
// SetCurrent so that the task queue puts the version in its versions info
_, err := s.FrontendClient().SetWorkerDeploymentCurrentVersion(ctx, &workflowservice.SetWorkerDeploymentCurrentVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv1.DeploymentSeries(),
Version: tv1.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv1.ClientIdentity(),
})
s.Nil(err)
// new version with a different registered task-queue
tv2 := tv.WithBuildIDNumber(2).WithTaskQueue(tv.Any().String())
s.startVersionWorkflow(ctx, tv2)
// SetRampingVersion to tv2
_, err = s.FrontendClient().SetWorkerDeploymentRampingVersion(ctx, &workflowservice.SetWorkerDeploymentRampingVersionRequest{
Namespace: s.Namespace().String(),
DeploymentName: tv2.DeploymentSeries(),
Version: tv2.DeploymentVersionString(),
ConflictToken: nil,
Identity: tv2.ClientIdentity(),
IgnoreMissingTaskQueues: false,
})
// SetRampingVersion to tv2 should succeed as task_queue_1, despite missing from the new current version, has no backlogged tasks/add-rate > 0
s.Nil(err)
}
func (s *DeploymentVersionSuite) startWorkflow(
tv *testvars.TestVars,
override *workflowpb.VersioningOverride,
) string {
request := &workflowservice.StartWorkflowExecutionRequest{
RequestId: tv.Any().String(),
Namespace: s.Namespace().String(),
WorkflowId: tv.WorkflowID(),
WorkflowType: tv.WorkflowType(),
TaskQueue: tv.TaskQueue(),
Identity: tv.WorkerIdentity(),
VersioningOverride: override,
}
we, err0 := s.FrontendClient().StartWorkflowExecution(testcore.NewContext(), request)
s.NoError(err0)
return we.GetRunId()
}
func (s *DeploymentVersionSuite) tryDeleteVersion(
ctx context.Context,
tv *testvars.TestVars,
expectSuccess bool,
skipDrainage bool,
) {
_, err := s.FrontendClient().DeleteWorkerDeploymentVersion(ctx, &workflowservice.DeleteWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
SkipDrainage: skipDrainage,
})
if expectSuccess {
s.Nil(err)
} else {
s.Error(err)
}
}
func (s *DeploymentVersionSuite) TestUpdateVersionMetadata() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
tv1 := testvars.New(s).WithBuildIDNumber(1)
// Start deployment workflow 1 and wait for the deployment version to exist
s.startVersionWorkflow(ctx, tv1)
metadata := map[string]*commonpb.Payload{
"key1": {Data: testRandomMetadataValue},
"key2": {Data: testRandomMetadataValue},
}
_, err := s.FrontendClient().UpdateWorkerDeploymentVersionMetadata(ctx, &workflowservice.UpdateWorkerDeploymentVersionMetadataRequest{
Namespace: s.Namespace().String(),
Version: tv1.DeploymentVersionString(),
UpsertEntries: metadata,
RemoveEntries: nil,
})
s.NoError(err)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv1.DeploymentVersionString(),
})
s.NoError(err)
// validating the metadata
entries := resp.GetWorkerDeploymentVersionInfo().GetMetadata().GetEntries()
s.Equal(2, len(entries))
s.Equal(testRandomMetadataValue, entries["key1"].Data)
s.Equal(testRandomMetadataValue, entries["key2"].Data)
// Remove all the entries
_, err = s.FrontendClient().UpdateWorkerDeploymentVersionMetadata(ctx, &workflowservice.UpdateWorkerDeploymentVersionMetadataRequest{
Namespace: s.Namespace().String(),
Version: tv1.DeploymentVersionString(),
UpsertEntries: nil,
RemoveEntries: []string{"key1", "key2"},
})
s.NoError(err)
resp, err = s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv1.DeploymentVersionString(),
})
s.NoError(err)
entries = resp.GetWorkerDeploymentVersionInfo().GetMetadata().GetEntries()
s.Equal(0, len(entries))
}
func (s *DeploymentVersionSuite) checkVersionDrainage(
ctx context.Context,
tv *testvars.TestVars,
expectedDrainageInfo *deploymentpb.VersionDrainageInfo,
addGracePeriod, addRefreshInterval bool,
) {
waitFor := 5 * time.Second
if addGracePeriod {
waitFor += testVersionDrainageVisibilityGracePeriod
}
if addRefreshInterval {
waitFor += testVersionDrainageRefreshInterval
}
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
a.NoError(err)
dInfo := resp.GetWorkerDeploymentVersionInfo().GetDrainageInfo()
a.Equal(expectedDrainageInfo.Status, dInfo.GetStatus())
if expectedDrainageInfo.LastCheckedTime != nil {
a.Equal(expectedDrainageInfo.LastCheckedTime, dInfo.GetLastCheckedTime())
}
if expectedDrainageInfo.LastChangedTime != nil {
a.Equal(expectedDrainageInfo.LastChangedTime, dInfo.GetLastChangedTime())
}
}, waitFor, time.Millisecond*100)
}
func (s *DeploymentVersionSuite) checkVersionIsCurrent(ctx context.Context, tv *testvars.TestVars) {
// Querying the Deployment
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkerDeploymentVersion(ctx, &workflowservice.DescribeWorkerDeploymentVersionRequest{
Namespace: s.Namespace().String(),
Version: tv.DeploymentVersionString(),
})
if !a.NoError(err) {
return
}
a.Equal(tv.DeploymentVersionString(), resp.GetWorkerDeploymentVersionInfo().GetVersion())
a.NotNil(resp.GetWorkerDeploymentVersionInfo().GetCurrentSinceTime())
}, time.Second*10, time.Millisecond*1000)
}
func (s *DeploymentVersionSuite) checkDescribeWorkflowAfterOverride(
ctx context.Context,
wf *commonpb.WorkflowExecution,
expectedOverride *workflowpb.VersioningOverride,
) {
s.EventuallyWithT(func(t *assert.CollectT) {
a := assert.New(t)
resp, err := s.FrontendClient().DescribeWorkflowExecution(ctx, &workflowservice.DescribeWorkflowExecutionRequest{
Namespace: s.Namespace().String(),
Execution: wf,
})
a.NoError(err)
a.NotNil(resp)
a.NotNil(resp.GetWorkflowExecutionInfo())
a.Equal(expectedOverride.GetBehavior(), resp.GetWorkflowExecutionInfo().GetVersioningInfo().GetVersioningOverride().GetBehavior())
a.Equal(expectedOverride.GetPinnedVersion(), resp.GetWorkflowExecutionInfo().GetVersioningInfo().GetVersioningOverride().GetPinnedVersion())
}, 5*time.Second, 50*time.Millisecond)
}