diff --git a/api/v1alpha1/temporalworker_webhook.go b/api/v1alpha1/temporalworker_webhook.go index 5037eda0..4b269dc8 100644 --- a/api/v1alpha1/temporalworker_webhook.go +++ b/api/v1alpha1/temporalworker_webhook.go @@ -16,11 +16,11 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/temporalio/temporal-worker-controller/internal/defaults" ) const ( - defaultScaledownDelay = 1 * time.Hour - defaultDeleteDelay = 24 * time.Hour maxTemporalWorkerDeploymentNameLen = 63 ) @@ -42,13 +42,27 @@ func (r *TemporalWorkerDeployment) Default(ctx context.Context, obj runtime.Obje return apierrors.NewBadRequest("expected a TemporalWorkerDeployment") } - if dep.Spec.SunsetStrategy.ScaledownDelay == nil { - dep.Spec.SunsetStrategy.ScaledownDelay = &v1.Duration{Duration: defaultScaledownDelay} + if err := dep.Spec.Default(ctx); err != nil { + return err } - if dep.Spec.SunsetStrategy.DeleteDelay == nil { - dep.Spec.SunsetStrategy.DeleteDelay = &v1.Duration{Duration: defaultDeleteDelay} + return nil +} + +func (s *TemporalWorkerDeploymentSpec) Default(ctx context.Context) error { + if s.SunsetStrategy.ScaledownDelay == nil { + s.SunsetStrategy.ScaledownDelay = &v1.Duration{Duration: defaults.ScaledownDelay} } + + if s.SunsetStrategy.DeleteDelay == nil { + s.SunsetStrategy.DeleteDelay = &v1.Duration{Duration: defaults.DeleteDelay} + } + + if s.MaxVersions == nil { + maxVersions := int32(defaults.MaxVersions) + s.MaxVersions = &maxVersions + } + return nil } diff --git a/api/v1alpha1/temporalworker_webhook_test.go b/api/v1alpha1/temporalworker_webhook_test.go index f9d6ef8a..22d16589 100644 --- a/api/v1alpha1/temporalworker_webhook_test.go +++ b/api/v1alpha1/temporalworker_webhook_test.go @@ -154,3 +154,53 @@ func TestTemporalWorkerDeployment_ValidateDelete(t *testing.T) { assert.NoError(t, err) assert.Nil(t, warnings) } + +func TestTemporalWorkerDeployment_Default(t *testing.T) { + tests := map[string]struct { + obj runtime.Object + expected func(t *testing.T, obj *temporaliov1alpha1.TemporalWorkerDeployment) + }{ + "sets default maxVersions": { + obj: testhelpers.MakeTWDWithName("default-max-versions"), + expected: func(t *testing.T, obj *temporaliov1alpha1.TemporalWorkerDeployment) { + require.NotNil(t, obj.Spec.MaxVersions) + assert.Equal(t, int32(75), *obj.Spec.MaxVersions) + }, + }, + "preserves existing maxVersions": { + obj: testhelpers.ModifyObj(testhelpers.MakeTWDWithName("preserve-max-versions"), func(obj *temporaliov1alpha1.TemporalWorkerDeployment) *temporaliov1alpha1.TemporalWorkerDeployment { + maxVersions := int32(100) + obj.Spec.MaxVersions = &maxVersions + return obj + }), + expected: func(t *testing.T, obj *temporaliov1alpha1.TemporalWorkerDeployment) { + require.NotNil(t, obj.Spec.MaxVersions) + assert.Equal(t, int32(100), *obj.Spec.MaxVersions) + }, + }, + "sets default sunset strategy delays": { + obj: testhelpers.MakeTWDWithName("default-sunset-delays"), + expected: func(t *testing.T, obj *temporaliov1alpha1.TemporalWorkerDeployment) { + require.NotNil(t, obj.Spec.SunsetStrategy.ScaledownDelay) + assert.Equal(t, time.Hour, obj.Spec.SunsetStrategy.ScaledownDelay.Duration) + require.NotNil(t, obj.Spec.SunsetStrategy.DeleteDelay) + assert.Equal(t, 24*time.Hour, obj.Spec.SunsetStrategy.DeleteDelay.Duration) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + webhook := &temporaliov1alpha1.TemporalWorkerDeployment{} + + err := webhook.Default(ctx, tc.obj) + require.NoError(t, err) + + obj, ok := tc.obj.(*temporaliov1alpha1.TemporalWorkerDeployment) + require.True(t, ok) + + tc.expected(t, obj) + }) + } +} diff --git a/api/v1alpha1/worker_types.go b/api/v1alpha1/worker_types.go index a48f875b..a107ce85 100644 --- a/api/v1alpha1/worker_types.go +++ b/api/v1alpha1/worker_types.go @@ -60,6 +60,18 @@ type TemporalWorkerDeploymentSpec struct { // TODO(jlegrone): add godoc WorkerOptions WorkerOptions `json:"workerOptions"` + + // MaxVersions defines the maximum number of worker deployment versions allowed. + // This helps prevent hitting Temporal's default limit of 100 versions per deployment. + // Defaults to 75. Users can override this by explicitly setting a higher value in + // the CRD, but should exercise caution: once the server's version limit is reached, + // Temporal attempts to delete an eligible version. If no version is eligible for deletion, + // new deployments get blocked which prevents the controller from making progress. + // This limit can be adjusted server-side by setting `matching.maxVersionsInDeployment` + // in dynamicconfig. + // +optional + // +kubebuilder:validation:Minimum=1 + MaxVersions *int32 `json:"maxVersions,omitempty"` } // VersionStatus indicates the status of a version. @@ -124,6 +136,11 @@ type TemporalWorkerDeploymentStatus struct { // LastModifierIdentity is the identity of the client that most recently modified the worker deployment. // +optional LastModifierIdentity string `json:"lastModifierIdentity,omitempty"` + + // VersionCount is the total number of versions currently known by the worker deployment. + // This includes current, target, ramping, and deprecated versions. + // +optional + VersionCount int32 `json:"versionCount,omitempty"` } // WorkflowExecutionStatus describes the current state of a workflow. @@ -312,8 +329,6 @@ type QueueStatistics struct { //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // TemporalWorkerDeployment is the Schema for the temporalworkerdeployments API -// -// TODO(jlegrone): Implement default/validate interface https://book.kubebuilder.io/cronjob-tutorial/webhook-implementation.html type TemporalWorkerDeployment struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8f4af3d9..911e9524 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -421,6 +421,11 @@ func (in *TemporalWorkerDeploymentSpec) DeepCopyInto(out *TemporalWorkerDeployme in.RolloutStrategy.DeepCopyInto(&out.RolloutStrategy) in.SunsetStrategy.DeepCopyInto(&out.SunsetStrategy) out.WorkerOptions = in.WorkerOptions + if in.MaxVersions != nil { + in, out := &in.MaxVersions, &out.MaxVersions + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemporalWorkerDeploymentSpec. diff --git a/internal/controller/state_mapper.go b/internal/controller/state_mapper.go index 980de67a..0bebdeb0 100644 --- a/internal/controller/state_mapper.go +++ b/internal/controller/state_mapper.go @@ -59,6 +59,9 @@ func (m *stateMapper) mapToStatus(targetVersionID string) *v1alpha1.TemporalWork } status.DeprecatedVersions = deprecatedVersions + // Set version count from temporal state (directly from VersionSummaries via Versions map) + status.VersionCount = int32(len(m.temporalState.Versions)) + return status } diff --git a/internal/controller/state_mapper_test.go b/internal/controller/state_mapper_test.go index bab00761..436977d9 100644 --- a/internal/controller/state_mapper_test.go +++ b/internal/controller/state_mapper_test.go @@ -179,6 +179,10 @@ func TestMapToStatus(t *testing.T) { assert.NotNil(t, status.DeprecatedVersions[0].DrainedSince) assert.Equal(t, drainedSince.Unix(), status.DeprecatedVersions[0].DrainedSince.Time.Unix()) + + // Verify version count is set correctly from VersionSummaries (via Versions map) + // Should count: worker.v1, worker.v2, worker.v3 (3 versions total) + assert.Equal(t, int32(3), status.VersionCount) } func TestMapWorkerDeploymentVersion(t *testing.T) { diff --git a/internal/defaults/defaults.go b/internal/defaults/defaults.go new file mode 100644 index 00000000..87b78e6a --- /dev/null +++ b/internal/defaults/defaults.go @@ -0,0 +1,14 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2024 Datadog, Inc. +package defaults + +import "time" + +// Default values for TemporalWorkerDeploymentSpec fields +const ( + ScaledownDelay = 1 * time.Hour + DeleteDelay = 24 * time.Hour + ServerMaxVersions = 100 + MaxVersions = int32(ServerMaxVersions * 0.75) +) diff --git a/internal/planner/planner.go b/internal/planner/planner.go index 0fa6cf18..51a42b04 100644 --- a/internal/planner/planner.go +++ b/internal/planner/planner.go @@ -57,22 +57,6 @@ type Config struct { RolloutStrategy temporaliov1alpha1.RolloutStrategy } -// ScaledownDelay returns the scaledown delay from the sunset strategy -func getScaledownDelay(spec *temporaliov1alpha1.TemporalWorkerDeploymentSpec) time.Duration { - if spec.SunsetStrategy.ScaledownDelay == nil { - return 0 - } - return spec.SunsetStrategy.ScaledownDelay.Duration -} - -// DeleteDelay returns the delete delay from the sunset strategy -func getDeleteDelay(spec *temporaliov1alpha1.TemporalWorkerDeploymentSpec) time.Duration { - if spec.SunsetStrategy.DeleteDelay == nil { - return 0 - } - return spec.SunsetStrategy.DeleteDelay.Duration -} - // GeneratePlan creates a plan for updating the worker deployment func GeneratePlan( l logr.Logger, @@ -89,7 +73,7 @@ func GeneratePlan( // Add delete/scale operations based on version status plan.DeleteDeployments = getDeleteDeployments(k8sState, status, spec) plan.ScaleDeployments = getScaleDeployments(k8sState, status, spec) - plan.ShouldCreateDeployment = shouldCreateDeployment(status) + plan.ShouldCreateDeployment = shouldCreateDeployment(status, spec) // Determine if we need to start any test workflows plan.TestWorkflows = getTestWorkflows(status, config) @@ -127,7 +111,7 @@ func getDeleteDeployments( // Deleting a deployment is only possible when: // 1. The deployment has been drained for deleteDelay + scaledownDelay. // 2. The deployment is scaled to 0 replicas. - if (time.Since(version.DrainedSince.Time) > getDeleteDelay(spec)+getScaledownDelay(spec)) && + if (time.Since(version.DrainedSince.Time) > spec.SunsetStrategy.DeleteDelay.Duration+spec.SunsetStrategy.ScaledownDelay.Duration) && *d.Spec.Replicas == 0 { deleteDeployments = append(deleteDeployments, d) } @@ -193,7 +177,7 @@ func getScaleDeployments( scaleDeployments[version.Deployment] = uint32(replicas) } case temporaliov1alpha1.VersionStatusDrained: - if time.Since(version.DrainedSince.Time) > getScaledownDelay(spec) { + if time.Since(version.DrainedSince.Time) > spec.SunsetStrategy.ScaledownDelay.Duration { // TODO(jlegrone): Compute scale based on load? Or percentage of replicas? // Scale down drained deployments after delay if d.Spec.Replicas != nil && *d.Spec.Replicas != 0 { @@ -209,8 +193,24 @@ func getScaleDeployments( // shouldCreateDeployment determines if a new deployment needs to be created func shouldCreateDeployment( status *temporaliov1alpha1.TemporalWorkerDeploymentStatus, + spec *temporaliov1alpha1.TemporalWorkerDeploymentSpec, ) bool { - return status.TargetVersion.Deployment == nil + // Check if target version already has a deployment + if status.TargetVersion.Deployment != nil { + return false + } + + // Check if we're at the version limit + maxVersions := int32(75) // Default from defaults.MaxVersions + if spec.MaxVersions != nil { + maxVersions = *spec.MaxVersions + } + + if status.VersionCount >= maxVersions { + return false + } + + return true } // getTestWorkflows determines which test workflows should be started diff --git a/internal/planner/planner_test.go b/internal/planner/planner_test.go index f6debed7..aaf85d2e 100644 --- a/internal/planner/planner_test.go +++ b/internal/planner/planner_test.go @@ -5,6 +5,7 @@ package planner import ( + "context" "testing" "time" @@ -98,6 +99,10 @@ func TestGeneratePlan(t *testing.T) { }, }, spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ + ScaledownDelay: &metav1.Duration{Duration: 0}, + DeleteDelay: &metav1.Duration{Duration: 0}, + }, Replicas: func() *int32 { r := int32(1); return &r }(), }, state: &temporal.TemporalWorkerState{}, @@ -205,6 +210,33 @@ func TestGeneratePlan(t *testing.T) { expectConfigSetCurrent: func() *bool { b := false; return &b }(), // Should NOT set current (already current) expectConfigRampPercent: func() *float32 { f := float32(0); return &f }(), // Should reset ramp to 0 }, + { + name: "should not create deployment when version limit is reached", + k8sState: &k8s.DeploymentState{ + Deployments: map[string]*appsv1.Deployment{}, + DeploymentsByTime: []*appsv1.Deployment{}, + DeploymentRefs: map[string]*v1.ObjectReference{}, + }, + status: &temporaliov1alpha1.TemporalWorkerDeploymentStatus{ + VersionCount: 5, + TargetVersion: temporaliov1alpha1.TargetWorkerDeploymentVersion{ + BaseWorkerDeploymentVersion: temporaliov1alpha1.BaseWorkerDeploymentVersion{ + VersionID: "test/namespace.new", + Status: temporaliov1alpha1.VersionStatusNotRegistered, + Deployment: nil, + }, + }, + }, + spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + MaxVersions: func() *int32 { i := int32(5); return &i }(), + Replicas: func() *int32 { r := int32(1); return &r }(), + }, + state: &temporal.TemporalWorkerState{}, + config: &Config{ + RolloutStrategy: temporaliov1alpha1.RolloutStrategy{}, + }, + expectCreate: false, + }, } for _, tc := range testCases { @@ -263,12 +295,13 @@ func TestGetDeleteDeployments(t *testing.T) { }, spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ - DeleteDelay: &metav1.Duration{ - Duration: 4 * time.Hour, - }, + DeleteDelay: &metav1.Duration{Duration: 4 * time.Hour}, }, Replicas: func() *int32 { r := int32(1); return &r }(), }, + config: &Config{ + RolloutStrategy: temporaliov1alpha1.RolloutStrategy{}, + }, expectDeletes: 1, }, { @@ -294,9 +327,7 @@ func TestGetDeleteDeployments(t *testing.T) { }, spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ - DeleteDelay: &metav1.Duration{ - Duration: 4 * time.Hour, - }, + DeleteDelay: &metav1.Duration{Duration: 4 * time.Hour}, }, Replicas: func() *int32 { r := int32(1); return &r }(), }, @@ -337,6 +368,8 @@ func TestGetDeleteDeployments(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + err := tc.spec.Default(context.Background()) + require.NoError(t, err) deletes := getDeleteDeployments(tc.k8sState, tc.status, tc.spec) assert.Equal(t, tc.expectDeletes, len(deletes), "unexpected number of deletes") }) @@ -410,6 +443,9 @@ func TestGetScaleDeployments(t *testing.T) { }, }, spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ + ScaledownDelay: &metav1.Duration{Duration: 0}, + }, Replicas: func() *int32 { r := int32(1); return &r }(), }, expectScales: 1, @@ -562,6 +598,7 @@ func TestShouldCreateDeployment(t *testing.T) { testCases := []struct { name string status *temporaliov1alpha1.TemporalWorkerDeploymentStatus + spec *temporaliov1alpha1.TemporalWorkerDeploymentSpec expectCreates bool }{ { @@ -575,6 +612,9 @@ func TestShouldCreateDeployment(t *testing.T) { }, }, }, + spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + Replicas: func() *int32 { r := int32(1); return &r }(), + }, expectCreates: false, }, { @@ -588,13 +628,70 @@ func TestShouldCreateDeployment(t *testing.T) { }, }, }, + spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + Replicas: func() *int32 { r := int32(1); return &r }(), + }, + expectCreates: true, + }, + { + name: "should not create when version limit is reached (default limit)", + status: &temporaliov1alpha1.TemporalWorkerDeploymentStatus{ + VersionCount: 75, // Default limit is 75 + TargetVersion: temporaliov1alpha1.TargetWorkerDeploymentVersion{ + BaseWorkerDeploymentVersion: temporaliov1alpha1.BaseWorkerDeploymentVersion{ + VersionID: "test/namespace.new", + Status: temporaliov1alpha1.VersionStatusNotRegistered, + Deployment: nil, + }, + }, + }, + spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + // MaxVersions is nil, so uses default of 75 + Replicas: func() *int32 { r := int32(1); return &r }(), + }, + expectCreates: false, + }, + { + name: "should not create when version limit is reached (custom limit)", + status: &temporaliov1alpha1.TemporalWorkerDeploymentStatus{ + VersionCount: 5, + TargetVersion: temporaliov1alpha1.TargetWorkerDeploymentVersion{ + BaseWorkerDeploymentVersion: temporaliov1alpha1.BaseWorkerDeploymentVersion{ + VersionID: "test/namespace.new", + Status: temporaliov1alpha1.VersionStatusNotRegistered, + Deployment: nil, + }, + }, + }, + spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + MaxVersions: func() *int32 { i := int32(5); return &i }(), + Replicas: func() *int32 { r := int32(1); return &r }(), + }, + expectCreates: false, + }, + { + name: "should create when below version limit", + status: &temporaliov1alpha1.TemporalWorkerDeploymentStatus{ + VersionCount: 4, + TargetVersion: temporaliov1alpha1.TargetWorkerDeploymentVersion{ + BaseWorkerDeploymentVersion: temporaliov1alpha1.BaseWorkerDeploymentVersion{ + VersionID: "test/namespace.new", + Status: temporaliov1alpha1.VersionStatusNotRegistered, + Deployment: nil, + }, + }, + }, + spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ + MaxVersions: func() *int32 { i := int32(5); return &i }(), + Replicas: func() *int32 { r := int32(1); return &r }(), + }, expectCreates: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - creates := shouldCreateDeployment(tc.status) + creates := shouldCreateDeployment(tc.status, tc.spec) assert.Equal(t, tc.expectCreates, creates, "unexpected create decision") }) } @@ -668,7 +765,7 @@ func TestGetTestWorkflows(t *testing.T) { BaseWorkerDeploymentVersion: temporaliov1alpha1.BaseWorkerDeploymentVersion{ VersionID: "test/namespace.123", Status: temporaliov1alpha1.VersionStatusInactive, - TaskQueues: []temporaliov1alpha1.TaskQueue{}, // Empty + TaskQueues: []temporaliov1alpha1.TaskQueue{}, }, }, }, @@ -679,7 +776,7 @@ func TestGetTestWorkflows(t *testing.T) { }, }, }, - expectWorkflows: 0, // No task queues, no workflows + expectWorkflows: 0, }, { name: "all test workflows already running", @@ -712,7 +809,7 @@ func TestGetTestWorkflows(t *testing.T) { }, }, }, - expectWorkflows: 0, // All queues have workflows + expectWorkflows: 0, }, } @@ -733,7 +830,7 @@ func TestGetVersionConfigDiff(t *testing.T) { state *temporal.TemporalWorkerState expectConfig bool expectSetCurrent bool - expectRampPercent *float32 // Made pointer to handle nil case + expectRampPercent *float32 }{ { name: "all at once strategy", @@ -934,7 +1031,7 @@ func TestGetVersionConfig_ProgressiveRolloutEdgeCases(t *testing.T) { }, }, expectConfig: true, - expectSetCurrent: true, // Should become current after all steps + expectSetCurrent: true, }, "progressive rollout with nil RampingSince": { strategy: temporaliov1alpha1.RolloutStrategy{ @@ -956,7 +1053,7 @@ func TestGetVersionConfig_ProgressiveRolloutEdgeCases(t *testing.T) { Status: temporaliov1alpha1.VersionStatusInactive, HealthySince: &metav1.Time{Time: time.Now()}, }, - RampingSince: nil, // Not ramping yet + RampingSince: nil, RampLastModifiedAt: nil, }, }, @@ -995,15 +1092,15 @@ func TestGetVersionConfig_ProgressiveRolloutEdgeCases(t *testing.T) { }, }, expectConfig: true, - expectRampPercent: 50, // When set as current, ramp is 0 - expectSetCurrent: false, // At exactly 2 hours, it sets as current + expectRampPercent: 50, // When set as current, ramp is 0 + expectSetCurrent: false, }, "progressive rollout with zero ramp percentage step": { strategy: temporaliov1alpha1.RolloutStrategy{ Strategy: temporaliov1alpha1.UpdateProgressive, Steps: []temporaliov1alpha1.RolloutStep{ {RampPercentage: 25, PauseDuration: metav1.Duration{Duration: 30 * time.Minute}}, - {RampPercentage: 0, PauseDuration: metav1.Duration{Duration: 30 * time.Minute}}, // Zero ramp + {RampPercentage: 0, PauseDuration: metav1.Duration{Duration: 30 * time.Minute}}, {RampPercentage: 50, PauseDuration: metav1.Duration{Duration: 30 * time.Minute}}, }, }, @@ -1064,7 +1161,7 @@ func TestGetVersionConfig_ProgressiveRolloutEdgeCases(t *testing.T) { }, expectConfig: true, expectRampPercent: 0, - expectSetCurrent: true, // Past all steps, should be default + expectSetCurrent: true, }, } @@ -1395,45 +1492,6 @@ func TestGetVersionConfig_GateWorkflowValidation(t *testing.T) { } } -func TestSunsetStrategyDefaults(t *testing.T) { - testCases := []struct { - name string - spec *temporaliov1alpha1.TemporalWorkerDeploymentSpec - expectScale time.Duration - expectDelete time.Duration - }{ - { - name: "nil delays return zero", - spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ - SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ - ScaledownDelay: nil, - DeleteDelay: nil, - }, - }, - expectScale: 0, - expectDelete: 0, - }, - { - name: "specified delays are returned", - spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ - SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ - ScaledownDelay: &metav1.Duration{Duration: 2 * time.Hour}, - DeleteDelay: &metav1.Duration{Duration: 48 * time.Hour}, - }, - }, - expectScale: 2 * time.Hour, - expectDelete: 48 * time.Hour, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - assert.Equal(t, tc.expectScale, getScaledownDelay(tc.spec)) - assert.Equal(t, tc.expectDelete, getDeleteDelay(tc.spec)) - }) - } -} - func TestComplexVersionStateScenarios(t *testing.T) { testCases := []struct { name string @@ -1444,7 +1502,7 @@ func TestComplexVersionStateScenarios(t *testing.T) { state *temporal.TemporalWorkerState expectDeletes int expectScales int - expectVersions []string // Expected version IDs for scaling + expectVersions []string }{ { name: "multiple deprecated versions in different states", @@ -1550,11 +1608,12 @@ func TestComplexVersionStateScenarios(t *testing.T) { spec: &temporaliov1alpha1.TemporalWorkerDeploymentSpec{ SunsetStrategy: temporaliov1alpha1.SunsetStrategy{ ScaledownDelay: &metav1.Duration{Duration: 2 * time.Hour}, + DeleteDelay: &metav1.Duration{Duration: 24 * time.Hour}, }, Replicas: func() *int32 { r := int32(3); return &r }(), }, expectDeletes: 0, - expectScales: 0, // Should not scale down yet + expectScales: 0, }, }