Skip to content

Commit 26912c5

Browse files
author
Arvind Thirumurugan
committed
update generateStagesByStrategy function
Signed-off-by: Arvind Thirumurugan <[email protected]>
1 parent cd63274 commit 26912c5

File tree

4 files changed

+195
-25
lines changed

4 files changed

+195
-25
lines changed

.github/.copilot/breadcrumbs/2025-09-25-1430-initialize-refactor.md

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,10 +362,108 @@ func (r *Reconciler) computeRunStageStatus(
362362

363363
**Integration**: The `generateStagesByStrategy` function now converts concrete binding arrays to interfaces before calling `computeRunStageStatus`, maintaining compatibility while enabling interface-based processing.
364364

365+
### Phase 8: generateStagesByStrategy Refactor ✅
366+
367+
**Target**: `generateStagesByStrategy` function - computes stages based on StagedUpdateStrategy
368+
369+
**Implementation**: Applied interface-based pattern with new strategy resolver utility:
370+
371+
**Before**:
372+
```go
373+
func (r *Reconciler) generateStagesByStrategy(
374+
ctx context.Context,
375+
scheduledBindings []*placementv1beta1.ClusterResourceBinding,
376+
toBeDeletedBindings []*placementv1beta1.ClusterResourceBinding,
377+
updateRun *placementv1beta1.ClusterStagedUpdateRun,
378+
) error {
379+
// Direct Client.Get for ClusterStagedUpdateStrategy
380+
var updateStrategy placementv1beta1.ClusterStagedUpdateStrategy
381+
if err := r.Client.Get(ctx, client.ObjectKey{Name: updateRun.Spec.StagedUpdateStrategyName}, &updateStrategy); err != nil {
382+
// Direct field access
383+
updateRun.Status.StagedUpdateStrategySnapshot = &updateStrategy.Spec
384+
binding.Spec.TargetCluster
385+
}
386+
}
387+
```
388+
389+
**After**:
390+
```go
391+
func (r *Reconciler) generateStagesByStrategy(
392+
ctx context.Context,
393+
scheduledBindings []placementv1beta1.BindingObj,
394+
toBeDeletedBindings []placementv1beta1.BindingObj,
395+
updateRun placementv1beta1.StagedUpdateRunObj,
396+
) error {
397+
// Generic utility function handles type determination
398+
strategyKey := types.NamespacedName{
399+
Name: updateRunSpec.StagedUpdateStrategyName,
400+
Namespace: updateRun.GetNamespace(),
401+
}
402+
updateStrategy, err := controller.FetchStagedUpdateStrategyFromNamespacedName(ctx, r.Client, strategyKey)
403+
// Interface-based access
404+
updateRunStatus.StagedUpdateStrategySnapshot = updateStrategy.GetStagedUpdateStrategySpec()
405+
bindingSpec.TargetCluster
406+
}
407+
```
408+
409+
**Key Improvements**:
410+
1. **Generic Interface Parameters**: Uses `[]BindingObj` and `StagedUpdateStrategyObj` instead of concrete types
411+
2. **Strategy Resolver Utility**: Created `strategy_resolver.go` with `FetchStagedUpdateStrategyFromNamespacedName()`
412+
3. **Interface-Based Access**: Uses `GetStagedUpdateRunSpec()`, `GetStagedUpdateRunStatus()`, `GetBindingSpec()`
413+
4. **Interface-Based Status Updates**: Uses `SetStagedUpdateRunStatus()` instead of direct field assignment
414+
5. **Automatic Type Determination**: Namespace presence determines ClusterStagedUpdateStrategy vs StagedUpdateStrategy
415+
6. **Generic Logging**: Updated terminology from cluster-specific to generic
416+
417+
**New Utility**: Created `/pkg/utils/controller/strategy_resolver.go` following the same pattern as `binding_resolver.go`
418+
419+
### Phase 9: removeWaitTimeFromUpdateRunStatus Refactor ✅
420+
421+
**Target**: `removeWaitTimeFromUpdateRunStatus` utility function - removes waitTime from approval tasks
422+
423+
**Implementation**: Updated from concrete type to interface-based approach:
424+
425+
**Before**:
426+
```go
427+
func removeWaitTimeFromUpdateRunStatus(updateRun *placementv1beta1.ClusterStagedUpdateRun) {
428+
// Direct field access
429+
if updateRun.Status.StagedUpdateStrategySnapshot != nil {
430+
for i := range updateRun.Status.StagedUpdateStrategySnapshot.Stages {
431+
// Direct status modification
432+
updateRun.Status.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks[j].WaitTime = nil
433+
}
434+
}
435+
}
436+
```
437+
438+
**After**:
439+
```go
440+
func removeWaitTimeFromUpdateRunStatus(updateRun placementv1beta1.StagedUpdateRunObj) {
441+
// Interface-based access
442+
updateRunStatus := updateRun.GetStagedUpdateRunStatus()
443+
if updateRunStatus.StagedUpdateStrategySnapshot != nil {
444+
for i := range updateRunStatus.StagedUpdateStrategySnapshot.Stages {
445+
// Modify status copy
446+
updateRunStatus.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks[j].WaitTime = nil
447+
}
448+
// Update back using interface method
449+
updateRun.SetStagedUpdateRunStatus(*updateRunStatus)
450+
}
451+
}
452+
```
453+
454+
**Key Improvements**:
455+
1. **Generic Interface Parameter**: Uses `StagedUpdateRunObj` instead of `*ClusterStagedUpdateRun`
456+
2. **Interface-Based Status Access**: Uses `GetStagedUpdateRunStatus()` and `SetStagedUpdateRunStatus()`
457+
3. **Proper Status Management**: Gets status copy, modifies it, then sets it back
458+
4. **Eliminated Type Assertion**: Removed the type assertion workaround in `generateStagesByStrategy`
459+
460+
**Impact**: This utility function is now generic and works with both `ClusterStagedUpdateRun` and `StagedUpdateRun` objects.
461+
365462
## Current Status
366463
-`validatePlacement`: Fully generic, uses utility functions and interfaces
367464
-`determinePolicySnapshot`: Fully generic, uses utility functions and interfaces, unnecessary switch removed
368465
-`collectScheduledClusters`: Fully generic, uses utility functions and interfaces
369466
-`computeRunStageStatus`: Fully generic, uses interface-based access patterns
370-
- ⚠️ `initialize`: Still takes concrete ClusterStagedUpdateRun type, but now calls interface-based functions
371-
- ⚠️ `generateStagesByStrategy`: Still takes concrete types but converts to interfaces for `computeRunStageStatus`
467+
-`generateStagesByStrategy`: Fully generic, uses interface-based access patterns and new strategy resolver utility
468+
-`removeWaitTimeFromUpdateRunStatus`: Fully generic utility function using interface-based approach
469+
- ⚠️ `initialize`: Still takes concrete ClusterStagedUpdateRun type, but now calls interface-based functions

pkg/controllers/updaterun/controller.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,15 +378,19 @@ func emitUpdateRunStatusMetric(updateRun *placementv1beta1.ClusterStagedUpdateRu
378378
klog.V(2).InfoS("There's no valid status condition on updateRun, status updating failed possibly", "updateRun", klog.KObj(updateRun))
379379
}
380380

381-
func removeWaitTimeFromUpdateRunStatus(updateRun *placementv1beta1.ClusterStagedUpdateRun) {
381+
func removeWaitTimeFromUpdateRunStatus(updateRun placementv1beta1.StagedUpdateRunObj) {
382382
// Remove waitTime from the updateRun status for AfterStageTask for type Approval.
383-
if updateRun.Status.StagedUpdateStrategySnapshot != nil {
384-
for i := range updateRun.Status.StagedUpdateStrategySnapshot.Stages {
385-
for j := range updateRun.Status.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks {
386-
if updateRun.Status.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks[j].Type == placementv1beta1.AfterStageTaskTypeApproval {
387-
updateRun.Status.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks[j].WaitTime = nil
383+
// Use interface methods to access and update status
384+
updateRunStatus := updateRun.GetStagedUpdateRunStatus()
385+
if updateRunStatus.StagedUpdateStrategySnapshot != nil {
386+
for i := range updateRunStatus.StagedUpdateStrategySnapshot.Stages {
387+
for j := range updateRunStatus.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks {
388+
if updateRunStatus.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks[j].Type == placementv1beta1.AfterStageTaskTypeApproval {
389+
updateRunStatus.StagedUpdateStrategySnapshot.Stages[i].AfterStageTasks[j].WaitTime = nil
388390
}
389391
}
390392
}
393+
// Update the status back using interface method
394+
updateRun.SetStagedUpdateRunStatus(*updateRunStatus)
391395
}
392396
}

pkg/controllers/updaterun/initialization.go

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ func (r *Reconciler) initialize(
9494
}
9595

9696
// Compute the stages based on the StagedUpdateStrategy.
97-
if err := r.generateStagesByStrategy(ctx, scheduledBindings, toBeDeletedBindings, updateRun); err != nil {
97+
// Use interface objects directly - scheduledBindingObjs and toBeDeletedBindingObjs already exist
98+
if err := r.generateStagesByStrategy(ctx, scheduledBindingObjs, toBeDeletedBindingObjs, updateRun); err != nil {
9899
return nil, nil, err
99100
}
100101
// Record the override snapshots associated with each cluster.
@@ -295,51 +296,73 @@ func (r *Reconciler) collectScheduledClusters(
295296
return selectedBindings, toBeDeletedBindings, nil
296297
}
297298

298-
// generateStagesByStrategy computes the stages based on the ClusterStagedUpdateStrategy referenced by the ClusterStagedUpdateRun.
299+
// generateStagesByStrategy computes the stages based on the StagedUpdateStrategy referenced by the StagedUpdateRun.
299300
func (r *Reconciler) generateStagesByStrategy(
300301
ctx context.Context,
301-
scheduledBindings []*placementv1beta1.ClusterResourceBinding,
302-
toBeDeletedBindings []*placementv1beta1.ClusterResourceBinding,
303-
updateRun *placementv1beta1.ClusterStagedUpdateRun,
302+
scheduledBindings []placementv1beta1.BindingObj,
303+
toBeDeletedBindings []placementv1beta1.BindingObj,
304+
updateRun placementv1beta1.StagedUpdateRunObj,
304305
) error {
305306
updateRunRef := klog.KObj(updateRun)
306-
// Fetch the StagedUpdateStrategy referenced by StagedUpdateStrategyName.
307-
var updateStrategy placementv1beta1.ClusterStagedUpdateStrategy
308-
if err := r.Client.Get(ctx, client.ObjectKey{Name: updateRun.Spec.StagedUpdateStrategyName}, &updateStrategy); err != nil {
309-
klog.ErrorS(err, "Failed to get StagedUpdateStrategy", "stagedUpdateStrategy", updateRun.Spec.StagedUpdateStrategyName, "clusterStagedUpdateRun", updateRunRef)
307+
308+
// Get updateRun spec using interface methods
309+
updateRunSpec := updateRun.GetStagedUpdateRunSpec()
310+
311+
// Create NamespacedName for the StagedUpdateStrategy
312+
// For ClusterStagedUpdateRun, namespace will be empty (cluster-scoped)
313+
// For StagedUpdateRun, namespace will be the actual namespace (namespace-scoped)
314+
strategyKey := types.NamespacedName{
315+
Name: updateRunSpec.StagedUpdateStrategyName,
316+
Namespace: updateRun.GetNamespace(),
317+
}
318+
319+
// Fetch the StagedUpdateStrategy using utility function
320+
// This automatically determines whether to fetch ClusterStagedUpdateStrategy or StagedUpdateStrategy
321+
updateStrategy, err := controller.FetchStagedUpdateStrategyFromNamespacedName(ctx, r.Client, strategyKey)
322+
if err != nil {
323+
klog.ErrorS(err, "Failed to get StagedUpdateStrategy", "stagedUpdateStrategy", updateRunSpec.StagedUpdateStrategyName, "stagedUpdateRun", updateRunRef)
310324
if apierrors.IsNotFound(err) {
311325
// we won't continue or retry the initialization if the StagedUpdateStrategy is not found.
312-
strategyNotFoundErr := controller.NewUserError(errors.New("referenced clusterStagedUpdateStrategy not found: " + updateRun.Spec.StagedUpdateStrategyName))
326+
strategyNotFoundErr := controller.NewUserError(errors.New("referenced stagedUpdateStrategy not found: " + updateRunSpec.StagedUpdateStrategyName))
313327
return fmt.Errorf("%w: %s", errInitializedFailed, strategyNotFoundErr.Error())
314328
}
315329
// other err can be retried.
316330
return controller.NewAPIServerError(true, err)
317331
}
318332

319333
// This won't change even if the stagedUpdateStrategy changes or is deleted after the updateRun is initialized.
320-
updateRun.Status.StagedUpdateStrategySnapshot = &updateStrategy.Spec
334+
// Use interface methods to access and update status
335+
updateRunStatus := updateRun.GetStagedUpdateRunStatus()
336+
updateStrategySpec := updateStrategy.GetStagedUpdateStrategySpec()
337+
updateRunStatus.StagedUpdateStrategySnapshot = updateStrategySpec
338+
updateRun.SetStagedUpdateRunStatus(*updateRunStatus)
339+
321340
// Remove waitTime from the updateRun status for AfterStageTask for type Approval.
322341
removeWaitTimeFromUpdateRunStatus(updateRun)
323342

324343
// Compute the update stages.
325-
// Convert concrete binding arrays to interface arrays
326-
scheduledBindingObjs := controller.ConvertCRBArrayToBindingObjs(scheduledBindings)
327-
if err := r.computeRunStageStatus(ctx, scheduledBindingObjs, updateRun); err != nil {
344+
// scheduledBindings already are interface objects, no conversion needed
345+
if err := r.computeRunStageStatus(ctx, scheduledBindings, updateRun); err != nil {
328346
return err
329347
}
330348
toBeDeletedClusters := make([]placementv1beta1.ClusterUpdatingStatus, len(toBeDeletedBindings))
331349
for i, binding := range toBeDeletedBindings {
332-
klog.V(2).InfoS("Adding a cluster to the delete stage", "cluster", binding.Spec.TargetCluster, "clusterStagedUpdateStrategy", updateStrategy.Name, "clusterStagedUpdateRun", updateRunRef)
333-
toBeDeletedClusters[i].ClusterName = binding.Spec.TargetCluster
350+
bindingSpec := binding.GetBindingSpec()
351+
klog.V(2).InfoS("Adding a cluster to the delete stage", "cluster", bindingSpec.TargetCluster, "stagedUpdateStrategy", updateStrategy.GetName(), "stagedUpdateRun", updateRunRef)
352+
toBeDeletedClusters[i].ClusterName = bindingSpec.TargetCluster
334353
}
335354
// Sort the clusters in the deletion stage by their names.
336355
sort.Slice(toBeDeletedClusters, func(i, j int) bool {
337356
return toBeDeletedClusters[i].ClusterName < toBeDeletedClusters[j].ClusterName
338357
})
339-
updateRun.Status.DeletionStageStatus = &placementv1beta1.StageUpdatingStatus{
358+
359+
// Update deletion stage status using interface methods
360+
updateRunStatus = updateRun.GetStagedUpdateRunStatus()
361+
updateRunStatus.DeletionStageStatus = &placementv1beta1.StageUpdatingStatus{
340362
StageName: placementv1beta1.UpdateRunDeleteStageName,
341363
Clusters: toBeDeletedClusters,
342364
}
365+
updateRun.SetStagedUpdateRunStatus(*updateRunStatus)
343366
return nil
344367
}
345368

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
Copyright 2025 The KubeFleet Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controller
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/apimachinery/pkg/types"
23+
"sigs.k8s.io/controller-runtime/pkg/client"
24+
25+
placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1"
26+
)
27+
28+
// FetchStagedUpdateStrategyFromNamespacedName resolves a NamespacedName to a concrete staged update strategy object that implements StagedUpdateStrategyObj.
29+
// If Namespace is empty, it fetches a ClusterStagedUpdateStrategy (cluster-scoped).
30+
// If Namespace is not empty, it fetches a StagedUpdateStrategy (namespace-scoped).
31+
func FetchStagedUpdateStrategyFromNamespacedName(ctx context.Context, c client.Reader, strategyKey types.NamespacedName) (placementv1beta1.StagedUpdateStrategyObj, error) {
32+
var strategy placementv1beta1.StagedUpdateStrategyObj
33+
// If Namespace is empty, it's a cluster-scoped strategy (ClusterStagedUpdateStrategy)
34+
if strategyKey.Namespace == "" {
35+
strategy = &placementv1beta1.ClusterStagedUpdateStrategy{}
36+
} else {
37+
// Otherwise, it's a namespaced strategy (StagedUpdateStrategy)
38+
strategy = &placementv1beta1.StagedUpdateStrategy{}
39+
}
40+
err := c.Get(ctx, strategyKey, strategy)
41+
if err != nil {
42+
return nil, err
43+
}
44+
return strategy, nil
45+
}

0 commit comments

Comments
 (0)