Files
tasks/common/worker_versioning/worker_versioning_test.go
8912c4e900 Handle Unversioned Current and Ramping Version (#7244)
## What changed?
- The initial current version of a worker deployment is now
`"__unversioned__"` instead of `""`
- When the ramping version of a worker deployment becomes unversioned,
the deployment tells all the task queues in the current version that
they now have an unversioned ramp.
- If the unversioned ramp is promoted to current version, or if the
unversioned ramping version is unset, the deployment tells all the task
queues of the previous current version that they no longer have an
unversioned ramp.

## Why?
<!-- Tell your future self why have you made these changes -->

## How did you test it?
- Made sure all existing WorkerDeploymentSuite and
DeploymentVersionSuite tests pass
- Wrote new tests in WorkerDeploymentSuite for the specific edge cases
that this code handles:
- `TestSetCurrentVersion_Unversioned_NoRamp`: Test that when current
version changes from versioned -> unversioned, the task queues become
unversioned
- `TestSetCurrentVersion_Unversioned_PromoteUnversionedRamp`: Test that
when the current version changes from versioned -> unversioned and
unversioned was previously ramping, the task queues become unversioned
with no more unversioned ramp
- `TestSetRampingVersion_Unversioned_UnversionedCurrent`: Test that this
fails with "Ramping version __unversioned__ is already current" error
- `TestSetRampingVersion_Unversioned_VersionedCurrent`: Test that the
ramping version of the current version's task queues becomes unversioned

## 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) -->

---------

Co-authored-by: Shivam <[email protected]>
Co-authored-by: Shahab Tajik <[email protected]>
Co-authored-by: Shivam Saraf <[email protected]>
Co-authored-by: ShahabT <[email protected]>
2025-02-06 15:41:35 -08:00

183 lines
7.3 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package worker_versioning
import (
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
deploymentspb "go.temporal.io/server/api/deployment/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
"go.temporal.io/server/common/primitives/timestamp"
)
var (
v1 = &deploymentspb.WorkerDeploymentVersion{
BuildId: "v1",
DeploymentName: "foo",
}
v2 = &deploymentspb.WorkerDeploymentVersion{
BuildId: "v2",
DeploymentName: "foo",
}
v3 = &deploymentspb.WorkerDeploymentVersion{
BuildId: "v3",
DeploymentName: "foo",
}
)
func TestCalculateTaskQueueVersioningInfo(t *testing.T) {
t1 := timestamp.TimePtr(time.Now().Add(-2 * time.Hour))
t2 := timestamp.TimePtr(time.Now().Add(-time.Hour))
t3 := timestamp.TimePtr(time.Now())
tests := []struct {
name string
wantCurrent *deploymentspb.DeploymentVersionData
wantRamping *deploymentspb.DeploymentVersionData
data *persistencespb.DeploymentData
}{
{name: "nil data"},
{name: "empty data", data: &persistencespb.DeploymentData{}},
{name: "old data", wantCurrent: &deploymentspb.DeploymentVersionData{Version: v1, RoutingUpdateTime: t1},
data: &persistencespb.DeploymentData{
Deployments: []*persistencespb.DeploymentData_DeploymentDataItem{
{Deployment: DeploymentFromDeploymentVersion(v1), Data: &deploymentspb.TaskQueueData{LastBecameCurrentTime: t1}},
}},
},
{name: "old and new data", wantCurrent: &deploymentspb.DeploymentVersionData{Version: v2, RoutingUpdateTime: t2, CurrentSinceTime: t2},
data: &persistencespb.DeploymentData{
Deployments: []*persistencespb.DeploymentData_DeploymentDataItem{
{Deployment: DeploymentFromDeploymentVersion(v1), Data: &deploymentspb.TaskQueueData{LastBecameCurrentTime: t1}},
},
Versions: []*deploymentspb.DeploymentVersionData{
{Version: v2, CurrentSinceTime: t2, RoutingUpdateTime: t2},
},
},
},
{name: "two current + two ramping",
wantCurrent: &deploymentspb.DeploymentVersionData{Version: v2, RoutingUpdateTime: t2, CurrentSinceTime: t2},
wantRamping: &deploymentspb.DeploymentVersionData{Version: v3, RoutingUpdateTime: t3, RampPercentage: 20, RampingSinceTime: t1},
data: &persistencespb.DeploymentData{
Versions: []*deploymentspb.DeploymentVersionData{
{Version: v1, CurrentSinceTime: t1, RoutingUpdateTime: t1},
{Version: v2, CurrentSinceTime: t2, RoutingUpdateTime: t2},
{Version: v1, RampPercentage: 50, RoutingUpdateTime: t2, RampingSinceTime: t2},
{Version: v3, RampPercentage: 20, RoutingUpdateTime: t3, RampingSinceTime: t1},
},
},
},
{name: "ramp without current", wantRamping: &deploymentspb.DeploymentVersionData{Version: v3, RoutingUpdateTime: t3, RampPercentage: 20, RampingSinceTime: t3},
data: &persistencespb.DeploymentData{
Versions: []*deploymentspb.DeploymentVersionData{
{Version: v1, RampPercentage: 50, RoutingUpdateTime: t2, RampingSinceTime: t2},
{Version: v3, RampPercentage: 20, RoutingUpdateTime: t3, RampingSinceTime: t3},
},
},
},
{name: "ramp to unversioned",
wantRamping: &deploymentspb.DeploymentVersionData{Version: nil, RoutingUpdateTime: t2, RampPercentage: 20, RampingSinceTime: t2},
data: &persistencespb.DeploymentData{
Versions: []*deploymentspb.DeploymentVersionData{
{Version: v1, RampPercentage: 50, RoutingUpdateTime: t1, RampingSinceTime: t1},
// Passing only deployment name without version
{Version: nil, RampPercentage: 20, RoutingUpdateTime: t2, RampingSinceTime: t2},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
current, ramping := CalculateTaskQueueVersioningInfo(tt.data)
if !current.Equal(tt.wantCurrent) {
t.Errorf("got current = %v, want %v", current, tt.wantCurrent)
}
if !ramping.Equal(tt.wantRamping) {
t.Errorf("got ramping = %v, want %v", ramping, tt.wantRamping)
}
})
}
}
func TestFindDeploymentVersionForWorkflowID(t *testing.T) {
tests := []struct {
name string
current *deploymentspb.DeploymentVersionData
ramping *deploymentspb.DeploymentVersionData
want *deploymentspb.WorkerDeploymentVersion
}{
{name: "nil current and ramping info", want: nil},
{name: "with current version", current: &deploymentspb.DeploymentVersionData{Version: v1}, want: v1},
{name: "with full ramp", current: &deploymentspb.DeploymentVersionData{Version: v1}, ramping: &deploymentspb.DeploymentVersionData{Version: v2, RampPercentage: 100}, want: v2},
{name: "with full ramp to unversioned", current: &deploymentspb.DeploymentVersionData{Version: v1}, ramping: &deploymentspb.DeploymentVersionData{RampPercentage: 100}, want: nil},
{name: "with full ramp from unversioned", ramping: &deploymentspb.DeploymentVersionData{Version: v1, RampPercentage: 100}, want: v1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := FindDeploymentVersionForWorkflowID(tt.current, tt.ramping, "my-wf-id"); !got.Equal(tt.want) {
t.Errorf("FindDeploymentVersionForWorkflowID() = %v, want %v", got, tt.want)
}
})
}
}
func TestFindDeploymentVersionForWorkflowID_PartialRamp(t *testing.T) {
tests := []struct {
name string
from *deploymentspb.WorkerDeploymentVersion
to *deploymentspb.WorkerDeploymentVersion
}{
{name: "from v1 to v2", from: v1, to: v2},
{name: "from v1 to unversioned", from: v1},
{name: "from unversioned to v2", to: v2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var current *deploymentspb.DeploymentVersionData
var ramping *deploymentspb.DeploymentVersionData
if tt.from != nil {
current = &deploymentspb.DeploymentVersionData{
Version: tt.from,
}
}
ramping = &deploymentspb.DeploymentVersionData{
Version: tt.to,
RampPercentage: 30,
}
histogram := make(map[string]int)
runs := 1000000
for i := 0; i < runs; i++ {
v := FindDeploymentVersionForWorkflowID(current, ramping, "wf-"+strconv.Itoa(i))
histogram[v.GetBuildId()]++
}
assert.InEpsilon(t, .7*float64(runs), histogram[tt.from.GetBuildId()], .02)
assert.InEpsilon(t, .3*float64(runs), histogram[tt.to.GetBuildId()], .02)
})
}
}