Skip to content

Commit 92f96f1

Browse files
committed
track work deletion in binding as applied failed
Signed-off-by: Britania Rodriguez Reyes <britaniar@microsoft.com>
1 parent 84b5213 commit 92f96f1

File tree

3 files changed

+82
-30
lines changed

3 files changed

+82
-30
lines changed

pkg/controllers/workgenerator/controller.go

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,12 @@ func (r *Reconciler) Reconcile(ctx context.Context, req controllerruntime.Reques
155155

156156
workUpdated := false
157157
overrideSucceeded := false
158+
workDeleted := false
158159
// list all the corresponding works
159160
works, syncErr := r.listAllWorksAssociated(ctx, &resourceBinding)
160161
if syncErr == nil {
161162
// generate and apply the workUpdated works if we have all the works
162-
overrideSucceeded, workUpdated, syncErr = r.syncAllWork(ctx, &resourceBinding, works, &cluster)
163+
overrideSucceeded, workUpdated, workDeleted, syncErr = r.syncAllWork(ctx, &resourceBinding, works, &cluster)
163164
}
164165
// Reset the conditions and failed/drifted/diffed placements.
165166
for i := condition.OverriddenCondition; i < condition.TotalCondition; i++ {
@@ -220,10 +221,16 @@ func (r *Reconciler) Reconcile(ctx context.Context, req controllerruntime.Reques
220221
Message: "All of the works are synchronized to the latest",
221222
})
222223
switch {
224+
case workDeleted:
225+
// Some Work object(s) are being deleted; set a False Applied condition which signals
226+
// that resources are in the process of being applied.
227+
klog.V(2).InfoS("Some work being deleted", "resourceBinding", bindingRef)
228+
setBindingStatus(workDeleted, works, &resourceBinding)
229+
syncErr = controller.NewUserError(fmt.Errorf("some work objects are being deleted"))
223230
case !workUpdated:
224231
// The Work object itself is unchanged; refresh the cluster resource binding status
225232
// based on the status information reported on the Work object(s).
226-
setBindingStatus(works, &resourceBinding)
233+
setBindingStatus(workDeleted, works, &resourceBinding)
227234
case resourceBinding.Spec.ApplyStrategy == nil || resourceBinding.Spec.ApplyStrategy.Type != fleetv1beta1.ApplyStrategyTypeReportDiff:
228235
// The Work object itself has changed; set a False Applied condition which signals
229236
// that resources are in the process of being applied.
@@ -409,8 +416,9 @@ func (r *Reconciler) listAllWorksAssociated(ctx context.Context, resourceBinding
409416
// it returns
410417
// 1: if we apply the overrides successfully
411418
// 2: if we actually made any changes on the hub cluster
412-
func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1beta1.ClusterResourceBinding, existingWorks map[string]*fleetv1beta1.Work, cluster *clusterv1beta1.MemberCluster) (bool, bool, error) {
419+
func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1beta1.ClusterResourceBinding, existingWorks map[string]*fleetv1beta1.Work, cluster *clusterv1beta1.MemberCluster) (bool, bool, bool, error) {
413420
updateAny := atomic.NewBool(false)
421+
deletedAny := atomic.NewBool(false)
414422
resourceBindingRef := klog.KObj(resourceBinding)
415423

416424
// Refresh the apply strategy for all existing works.
@@ -435,17 +443,17 @@ func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1be
435443
})
436444
}
437445
if updateErr := errs.Wait(); updateErr != nil {
438-
return false, false, updateErr
446+
return false, false, false, updateErr
439447
}
440448

441449
// the hash256 function can handle empty list https://go.dev/play/p/_4HW17fooXM
442450
resourceOverrideSnapshotHash, err := resource.HashOf(resourceBinding.Spec.ResourceOverrideSnapshots)
443451
if err != nil {
444-
return false, false, controller.NewUnexpectedBehaviorError(err)
452+
return false, false, false, controller.NewUnexpectedBehaviorError(err)
445453
}
446454
clusterResourceOverrideSnapshotHash, err := resource.HashOf(resourceBinding.Spec.ClusterResourceOverrideSnapshots)
447455
if err != nil {
448-
return false, false, controller.NewUnexpectedBehaviorError(err)
456+
return false, false, false, controller.NewUnexpectedBehaviorError(err)
449457
}
450458
// TODO: check all work synced first before fetching the snapshots after we put ParentResourceOverrideSnapshotHashAnnotation and ParentClusterResourceOverrideSnapshotHashAnnotation in all the work objects
451459

@@ -456,22 +464,22 @@ func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1be
456464
// the resourceIndex is deleted but the works might still be up to date with the binding.
457465
if areAllWorkSynced(existingWorks, resourceBinding, resourceOverrideSnapshotHash, clusterResourceOverrideSnapshotHash) {
458466
klog.V(2).InfoS("All the works are synced with the resourceBinding even if the resource snapshot index is removed", "resourceBinding", resourceBindingRef)
459-
return true, updateAny.Load(), nil
467+
return true, updateAny.Load(), deletedAny.Load(), nil
460468
}
461-
return false, false, controller.NewUserError(err)
469+
return false, false, false, controller.NewUserError(err)
462470
}
463471
// TODO(RZ): handle errResourceNotFullyCreated error so we don't need to wait for all the snapshots to be created
464-
return false, false, err
472+
return false, false, false, err
465473
}
466474

467475
croMap, err := r.fetchClusterResourceOverrideSnapshots(ctx, resourceBinding)
468476
if err != nil {
469-
return false, false, err
477+
return false, false, false, err
470478
}
471479

472480
roMap, err := r.fetchResourceOverrideSnapshots(ctx, resourceBinding)
473481
if err != nil {
474-
return false, false, err
482+
return false, false, false, err
475483
}
476484

477485
// issue all the create/update requests for the corresponding works for each snapshot in parallel
@@ -484,15 +492,15 @@ func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1be
484492
workNamePrefix, err := getWorkNamePrefixFromSnapshotName(snapshot)
485493
if err != nil {
486494
klog.ErrorS(err, "Encountered a mal-formatted resource snapshot", "resourceSnapshot", klog.KObj(snapshot))
487-
return false, false, err
495+
return false, false, false, err
488496
}
489497
var simpleManifests []fleetv1beta1.Manifest
490498
for j := range snapshot.Spec.SelectedResources {
491499
selectedResource := snapshot.Spec.SelectedResources[j].DeepCopy()
492500
// TODO: override the content of the wrapped resource instead of the envelope itself
493501
resourceDeleted, overrideErr := r.applyOverrides(selectedResource, cluster, croMap, roMap)
494502
if overrideErr != nil {
495-
return false, false, overrideErr
503+
return false, false, false, overrideErr
496504
}
497505
if resourceDeleted {
498506
klog.V(2).InfoS("The resource is deleted by the override rules", "snapshot", klog.KObj(snapshot), "selectedResource", snapshot.Spec.SelectedResources[j])
@@ -503,14 +511,14 @@ func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1be
503511
var uResource unstructured.Unstructured
504512
if unMarshallErr := uResource.UnmarshalJSON(selectedResource.Raw); unMarshallErr != nil {
505513
klog.ErrorS(unMarshallErr, "work has invalid content", "snapshot", klog.KObj(snapshot), "selectedResource", selectedResource.Raw)
506-
return true, false, controller.NewUnexpectedBehaviorError(unMarshallErr)
514+
return true, false, false, controller.NewUnexpectedBehaviorError(unMarshallErr)
507515
}
508516
if uResource.GetObjectKind().GroupVersionKind() == utils.ConfigMapGVK &&
509517
len(uResource.GetAnnotations()[fleetv1beta1.EnvelopeConfigMapAnnotation]) != 0 {
510518
// get a work object for the enveloped configMap
511519
work, err := r.getConfigMapEnvelopWorkObj(ctx, workNamePrefix, resourceBinding, snapshot, &uResource, resourceOverrideSnapshotHash, clusterResourceOverrideSnapshotHash)
512520
if err != nil {
513-
return true, false, err
521+
return true, false, false, err
514522
}
515523
activeWork[work.Name] = work
516524
newWork = append(newWork, work)
@@ -545,6 +553,7 @@ func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1be
545553
}
546554

547555
// delete the works that are not associated with any resource snapshot
556+
deletedWork := make([]*fleetv1beta1.Work, len(existingWorks))
548557
for i := range existingWorks {
549558
work := existingWorks[i]
550559
if _, exist := activeWork[work.Name]; exist {
@@ -557,18 +566,20 @@ func (r *Reconciler) syncAllWork(ctx context.Context, resourceBinding *fleetv1be
557566
return controller.NewAPIServerError(false, err)
558567
}
559568
}
569+
deletedWork = append(deletedWork, work)
560570
klog.V(2).InfoS("Deleted the work that is not associated with any resource snapshot", "work", klog.KObj(work))
561-
updateAny.Store(true)
571+
//updateAny.Store(true)
572+
deletedAny.Store(true)
562573
return nil
563574
})
564575
}
565576

566577
// wait for all the create/update/delete requests to finish
567578
if updateErr := errs.Wait(); updateErr != nil {
568-
return true, false, updateErr
579+
return true, false, false, updateErr
569580
}
570-
klog.V(2).InfoS("Successfully synced all the work associated with the resourceBinding", "updateAny", updateAny.Load(), "resourceBinding", resourceBindingRef)
571-
return true, updateAny.Load(), nil
581+
klog.V(2).InfoS("Successfully synced all the work associated with the resourceBinding", "updateAny", updateAny.Load(), "deletedAny", deletedAny.Load(), "resourceBinding", resourceBindingRef)
582+
return true, updateAny.Load(), deletedAny.Load(), nil
572583
}
573584

574585
// syncApplyStrategy syncs the apply strategy specified on a ClusterResourceBinding object
@@ -845,7 +856,7 @@ const (
845856
)
846857

847858
// setBindingStatus sets the binding status based on the works associated with the binding.
848-
func setBindingStatus(works map[string]*fleetv1beta1.Work, resourceBinding *fleetv1beta1.ClusterResourceBinding) {
859+
func setBindingStatus(workDeleted bool, works map[string]*fleetv1beta1.Work, resourceBinding *fleetv1beta1.ClusterResourceBinding) {
849860
bindingRef := klog.KObj(resourceBinding)
850861

851862
// Note (chenyu1): the work generator will refresh the status of a ClusterResourceBinding using
@@ -871,7 +882,7 @@ func setBindingStatus(works map[string]*fleetv1beta1.Work, resourceBinding *flee
871882
} else {
872883
// Set the Applied and Available condition if (and only if) a ClientSideApply or ServerSideApply
873884
// apply strategy is currently being used.
874-
appliedSummarizedStatus = setAllWorkAppliedCondition(works, resourceBinding)
885+
appliedSummarizedStatus = setAllWorkAppliedCondition(workDeleted, works, resourceBinding)
875886
// Note that Fleet will only set the Available condition if the apply op itself is successful, i.e.,
876887
// the Applied condition is True.
877888
availabilitySummarizedStatus = setAllWorkAvailableCondition(works, resourceBinding)
@@ -1007,7 +1018,7 @@ func setBindingStatus(works map[string]*fleetv1beta1.Work, resourceBinding *flee
10071018
//
10081019
// The Applied condition of a ClusterResourceBinding object is set to True if and only if all the
10091020
// related Work objects have their Applied condition set to True.
1010-
func setAllWorkAppliedCondition(works map[string]*fleetv1beta1.Work, binding *fleetv1beta1.ClusterResourceBinding) workConditionSummarizedStatus {
1021+
func setAllWorkAppliedCondition(workDeleted bool, works map[string]*fleetv1beta1.Work, binding *fleetv1beta1.ClusterResourceBinding) workConditionSummarizedStatus {
10111022
// Fleet here makes a clear distinction between incomplete, failed, and successful apply operations.
10121023
// This is to ensure that stale apply information (esp. those set before
10131024
// an apply strategy change) will not leak into the current apply operations.
@@ -1038,9 +1049,20 @@ func setAllWorkAppliedCondition(works map[string]*fleetv1beta1.Work, binding *fl
10381049
}
10391050

10401051
switch {
1052+
case workDeleted:
1053+
// Some work objects are being deleted.
1054+
klog.V(2).InfoS("Some works have not yet completed the apply op as some are deleting", "binding", klog.KObj(binding))
1055+
binding.SetConditions(metav1.Condition{
1056+
Status: metav1.ConditionFalse,
1057+
Type: string(fleetv1beta1.ResourceBindingApplied),
1058+
Reason: condition.WorkNotAppliedReason,
1059+
Message: "Some work objects have been deleted. Some works have not yet completed the apply op.",
1060+
ObservedGeneration: binding.GetGeneration(),
1061+
})
1062+
return workConditionSummarizedStatusFalse
10411063
case !areAllWorksApplyOpsCompleted:
10421064
// Not all Work objects have completed the apply op.
1043-
klog.V(2).InfoS("Some works are not yet completed the apply op", "binding", klog.KObj(binding), "firstWorkWithIncompleteApplyOp", klog.KObj(firstWorkWithIncompleteApplyOp))
1065+
klog.V(2).InfoS("Some works have not yet completed the apply op", "binding", klog.KObj(binding), "firstWorkWithIncompleteApplyOp", klog.KObj(firstWorkWithIncompleteApplyOp))
10441066
binding.SetConditions(metav1.Condition{
10451067
Status: metav1.ConditionFalse,
10461068
Type: string(fleetv1beta1.ResourceBindingApplied),

pkg/controllers/workgenerator/controller_integration_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,7 +596,7 @@ var _ = Describe("Test Work Generator Controller", func() {
596596
})
597597
})
598598

599-
Context("Test Bound ClusterResourceBinding with a single resource snapshot with envelop objects", func() {
599+
Context("Test Bound ClusterResourceBinding with a single resource snapshot with envelop objects", Serial, func() {
600600
var masterSnapshot *placementv1beta1.ClusterResourceSnapshot
601601

602602
BeforeEach(func() {
@@ -1210,6 +1210,7 @@ var _ = Describe("Test Work Generator Controller", func() {
12101210
return errors.IsNotFound(err)
12111211
}, duration, interval).Should(BeTrue(), "controller should remove work in hub cluster that is no longer needed")
12121212
By(fmt.Sprintf("second work %s is deleted in %s", work.Name, work.Namespace))
1213+
verifyBindingStatusSyncedNotApplied(binding, false, false)
12131214
})
12141215

12151216
It("Should remove binding after all work associated with deleted bind are deleted", func() {

pkg/controllers/workgenerator/controller_test.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ func TestUpsertWork(t *testing.T) {
528528
func TestSetAllWorkAppliedCondition(t *testing.T) {
529529
tests := map[string]struct {
530530
works map[string]*fleetv1beta1.Work
531+
workDeleted bool
531532
generation int64
532533
wantAppliedCond metav1.Condition
533534
wantWorkAppliedCondSummaryStatus workConditionSummarizedStatus
@@ -565,7 +566,8 @@ func TestSetAllWorkAppliedCondition(t *testing.T) {
565566
},
566567
},
567568
},
568-
generation: 1,
569+
workDeleted: false,
570+
generation: 1,
569571
wantAppliedCond: metav1.Condition{
570572
Status: metav1.ConditionTrue,
571573
Type: string(fleetv1beta1.ResourceBindingApplied),
@@ -607,7 +609,8 @@ func TestSetAllWorkAppliedCondition(t *testing.T) {
607609
},
608610
},
609611
},
610-
generation: 1,
612+
workDeleted: false,
613+
generation: 1,
611614
wantAppliedCond: metav1.Condition{
612615
Status: metav1.ConditionFalse,
613616
Type: string(fleetv1beta1.ResourceBindingApplied),
@@ -649,7 +652,8 @@ func TestSetAllWorkAppliedCondition(t *testing.T) {
649652
},
650653
},
651654
},
652-
generation: 1,
655+
workDeleted: false,
656+
generation: 1,
653657
wantAppliedCond: metav1.Condition{
654658
Status: metav1.ConditionFalse,
655659
Type: string(fleetv1beta1.ResourceBindingApplied),
@@ -685,7 +689,8 @@ func TestSetAllWorkAppliedCondition(t *testing.T) {
685689
},
686690
},
687691
},
688-
generation: 1,
692+
workDeleted: false,
693+
generation: 1,
689694
wantAppliedCond: metav1.Condition{
690695
Status: metav1.ConditionFalse,
691696
Type: string(fleetv1beta1.ResourceBindingApplied),
@@ -694,6 +699,30 @@ func TestSetAllWorkAppliedCondition(t *testing.T) {
694699
},
695700
wantWorkAppliedCondSummaryStatus: workConditionSummarizedStatusIncomplete,
696701
},
702+
"one work was deleted": {
703+
works: map[string]*fleetv1beta1.Work{
704+
// appliedWork1 was deleted
705+
706+
"notAppliedWork2": {
707+
ObjectMeta: metav1.ObjectMeta{
708+
Name: "work2",
709+
Generation: 123,
710+
},
711+
Status: fleetv1beta1.WorkStatus{
712+
Conditions: []metav1.Condition{},
713+
},
714+
},
715+
},
716+
workDeleted: true,
717+
generation: 1,
718+
wantAppliedCond: metav1.Condition{
719+
Status: metav1.ConditionFalse,
720+
Type: string(fleetv1beta1.ResourceBindingApplied),
721+
Reason: condition.WorkNotAppliedReason,
722+
ObservedGeneration: 1,
723+
},
724+
wantWorkAppliedCondSummaryStatus: workConditionSummarizedStatusFalse,
725+
},
697726
}
698727
for name, tt := range tests {
699728
t.Run(name, func(t *testing.T) {
@@ -703,7 +732,7 @@ func TestSetAllWorkAppliedCondition(t *testing.T) {
703732
Generation: tt.generation,
704733
},
705734
}
706-
workAppliedCondSummaryStatus := setAllWorkAppliedCondition(tt.works, binding)
735+
workAppliedCondSummaryStatus := setAllWorkAppliedCondition(tt.workDeleted, tt.works, binding)
707736
if workAppliedCondSummaryStatus != tt.wantWorkAppliedCondSummaryStatus {
708737
t.Errorf("setAllWorkAppliedCondition() = %v, want %v", workAppliedCondSummaryStatus, tt.wantWorkAppliedCondSummaryStatus)
709738
}
@@ -2372,7 +2401,7 @@ func TestSetBindingStatus(t *testing.T) {
23722401
ApplyStrategy: tt.applyStrategy,
23732402
},
23742403
}
2375-
setBindingStatus(tt.works, binding)
2404+
setBindingStatus(false, tt.works, binding)
23762405
got := binding.Status.FailedPlacements
23772406
// setBindingStatus is using map to populate the placements.
23782407
// There is no default order in traversing the map.

0 commit comments

Comments
 (0)