Skip to content

Commit 342e85c

Browse files
fix: trigger reconcile on rule deletion-timestamp updates
The RuleReconciler watch used a bare GenerationChangedPredicate. When a rule with the cleanup-taints finalizer is deleted, the API server only sets DeletionTimestamp; it does not bump Generation. The predicate filtered the update and reconcileDelete never ran, leaving the rule in Terminating and the managed taints orphaned on nodes. Replace the predicate with one that also passes Update events where DeletionTimestamp transitions from unset to set, and add unit coverage so a future regression to a bare GenerationChangedPredicate fails the build instead of silently breaking deletion.
1 parent 4f6e4bb commit 342e85c

2 files changed

Lines changed: 61 additions & 1 deletion

File tree

internal/controller/nodereadinessrule_controller.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"sigs.k8s.io/controller-runtime/pkg/client"
3838
"sigs.k8s.io/controller-runtime/pkg/controller"
3939
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
40+
"sigs.k8s.io/controller-runtime/pkg/event"
4041
"sigs.k8s.io/controller-runtime/pkg/predicate"
4142

4243
readinessv1alpha1 "sigs.k8s.io/node-readiness-controller/api/v1alpha1"
@@ -78,12 +79,28 @@ func NewRuleReadinessController(mgr ctrl.Manager, clientset kubernetes.Interface
7879
}
7980
}
8081

82+
// rulePredicate also passes the DeletionTimestamp-set transition: the API
83+
// server does not bump Generation when it sets DeletionTimestamp, so a bare
84+
// GenerationChangedPredicate would drop deletion events for rules with a finalizer
85+
var rulePredicate = predicate.Funcs{
86+
UpdateFunc: func(e event.UpdateEvent) bool {
87+
if e.ObjectOld == nil || e.ObjectNew == nil {
88+
return false
89+
}
90+
if e.ObjectNew.GetGeneration() != e.ObjectOld.GetGeneration() {
91+
return true
92+
}
93+
return e.ObjectOld.GetDeletionTimestamp().IsZero() &&
94+
!e.ObjectNew.GetDeletionTimestamp().IsZero()
95+
},
96+
}
97+
8198
// SetupWithManager sets up the controller with the Manager.
8299
func (r *RuleReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
83100
return ctrl.NewControllerManagedBy(mgr).
84101
Named("nodereadiness-controller").
85102
WithOptions(controller.Options{MaxConcurrentReconciles: 1}).
86-
For(&readinessv1alpha1.NodeReadinessRule{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
103+
For(&readinessv1alpha1.NodeReadinessRule{}, builder.WithPredicates(rulePredicate)).
87104
Complete(r)
88105
}
89106

internal/controller/nodereadinessrule_controller_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"k8s.io/client-go/kubernetes/fake"
3131
"k8s.io/client-go/tools/record"
3232
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/event"
3334
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3435

3536
nodereadinessiov1alpha1 "sigs.k8s.io/node-readiness-controller/api/v1alpha1"
@@ -1380,4 +1381,46 @@ var _ = Describe("NodeReadinessRule Controller", func() {
13801381
}, time.Second*5).Should(BeTrue(), "All AppliedNodes should have corresponding NodeEvaluations")
13811382
})
13821383
})
1384+
1385+
Context("rulePredicate", func() {
1386+
newRule := func(generation int64, deletionTimestamp *metav1.Time) *nodereadinessiov1alpha1.NodeReadinessRule {
1387+
return &nodereadinessiov1alpha1.NodeReadinessRule{
1388+
ObjectMeta: metav1.ObjectMeta{
1389+
Name: "predicate-rule",
1390+
Generation: generation,
1391+
DeletionTimestamp: deletionTimestamp,
1392+
},
1393+
}
1394+
}
1395+
1396+
It("passes updates that bump generation", func() {
1397+
Expect(rulePredicate.Update(event.UpdateEvent{
1398+
ObjectOld: newRule(1, nil),
1399+
ObjectNew: newRule(2, nil),
1400+
})).To(BeTrue())
1401+
})
1402+
1403+
It("passes updates that set the deletion timestamp without bumping generation", func() {
1404+
now := metav1.Now()
1405+
Expect(rulePredicate.Update(event.UpdateEvent{
1406+
ObjectOld: newRule(1, nil),
1407+
ObjectNew: newRule(1, &now),
1408+
})).To(BeTrue(), "deletion-marker updates must trigger reconcile so the finalizer can be removed")
1409+
})
1410+
1411+
It("ignores status-only updates", func() {
1412+
Expect(rulePredicate.Update(event.UpdateEvent{
1413+
ObjectOld: newRule(1, nil),
1414+
ObjectNew: newRule(1, nil),
1415+
})).To(BeFalse())
1416+
})
1417+
1418+
It("ignores updates while the rule is already terminating", func() {
1419+
now := metav1.Now()
1420+
Expect(rulePredicate.Update(event.UpdateEvent{
1421+
ObjectOld: newRule(1, &now),
1422+
ObjectNew: newRule(1, &now),
1423+
})).To(BeFalse())
1424+
})
1425+
})
13831426
})

0 commit comments

Comments
 (0)