@@ -30,6 +30,7 @@ import (
3030 "k8s.io/apimachinery/pkg/labels"
3131 "k8s.io/apimachinery/pkg/runtime"
3232 "k8s.io/client-go/kubernetes"
33+ "k8s.io/client-go/tools/record"
3334 "k8s.io/client-go/util/retry"
3435 ctrl "sigs.k8s.io/controller-runtime"
3536 "sigs.k8s.io/controller-runtime/pkg/builder"
@@ -50,8 +51,9 @@ const (
5051// RuleReadinessController manages node taints based on readiness rules.
5152type RuleReadinessController struct {
5253 client.Client
53- Scheme * runtime.Scheme
54- clientset kubernetes.Interface
54+ Scheme * runtime.Scheme
55+ clientset kubernetes.Interface
56+ EventRecorder record.EventRecorder
5557
5658 // Cache for efficient rule lookup
5759 ruleCacheMutex sync.RWMutex
@@ -68,10 +70,11 @@ type RuleReconciler struct {
6870// NewRuleReadinessController creates a new controller.
6971func NewRuleReadinessController (mgr ctrl.Manager , clientset kubernetes.Interface ) * RuleReadinessController {
7072 return & RuleReadinessController {
71- Client : mgr .GetClient (),
72- Scheme : mgr .GetScheme (),
73- clientset : clientset ,
74- ruleCache : make (map [string ]* readinessv1alpha1.NodeReadinessRule ),
73+ Client : mgr .GetClient (),
74+ Scheme : mgr .GetScheme (),
75+ clientset : clientset ,
76+ EventRecorder : mgr .GetEventRecorderFor ("node-readiness-controller" ),
77+ ruleCache : make (map [string ]* readinessv1alpha1.NodeReadinessRule ),
7578 }
7679}
7780
@@ -87,6 +90,7 @@ func (r *RuleReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager)
8790// +kubebuilder:rbac:groups=readiness.node.x-k8s.io,resources=nodereadinessrules,verbs=get;list;watch;create;update;patch;delete
8891// +kubebuilder:rbac:groups=readiness.node.x-k8s.io,resources=nodereadinessrules/status,verbs=get;update;patch
8992// +kubebuilder:rbac:groups=readiness.node.x-k8s.io,resources=nodereadinessrules/finalizers,verbs=update
93+ // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
9094
9195func (r * RuleReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
9296 log := ctrl .LoggerFrom (ctx )
@@ -325,13 +329,15 @@ func (r *RuleReadinessController) evaluateRuleForNode(ctx context.Context, rule
325329 log .Info ("Evaluation result" , "node" , node .Name , "rule" , rule .Name ,
326330 "allConditionsSatisfied" , allConditionsSatisfied , "hasTaint" , currentlyHasTaint )
327331
332+ isFirstEvaluation := r .getPreviousNodeEvaluation (rule , node .Name ) == nil
333+
328334 var err error
329335
330336 switch {
331337 case shouldRemoveTaint && currentlyHasTaint :
332338 log .Info ("Removing taint" , "node" , node .Name , "rule" , rule .Name , "taint" , rule .Spec .Taint .Key )
333339
334- if err = r .removeTaintBySpec (ctx , node , rule .Spec .Taint ); err != nil {
340+ if err = r .removeTaintBySpec (ctx , node , rule .Spec .Taint , rule . Name ); err != nil {
335341 metrics .Failures .WithLabelValues (rule .Name , "RemoveTaintError" ).Inc ()
336342 return fmt .Errorf ("failed to remove taint: %w" , err )
337343 }
@@ -345,12 +351,20 @@ func (r *RuleReadinessController) evaluateRuleForNode(ctx context.Context, rule
345351 case ! shouldRemoveTaint && ! currentlyHasTaint :
346352 log .Info ("Adding taint" , "node" , node .Name , "rule" , rule .Name , "taint" , rule .Spec .Taint .Key )
347353
348- if err = r .addTaintBySpec (ctx , node , rule .Spec .Taint ); err != nil {
354+ if err = r .addTaintBySpec (ctx , node , rule .Spec .Taint , rule . Name ); err != nil {
349355 metrics .Failures .WithLabelValues (rule .Name , "AddTaintError" ).Inc ()
350356 return fmt .Errorf ("failed to add taint: %w" , err )
351357 }
352358 metrics .TaintOperations .WithLabelValues (rule .Name , "add" ).Inc ()
353359
360+ case ! shouldRemoveTaint && currentlyHasTaint :
361+ if isFirstEvaluation {
362+ log .Info ("Adopting pre-existing taint" , "node" , node .Name , "rule" , rule .Name , "taint" , rule .Spec .Taint .Key )
363+
364+ message := fmt .Sprintf ("Taint '%s:%s' is now managed by rule '%s'" , rule .Spec .Taint .Key , rule .Spec .Taint .Effect , rule .Name )
365+ r .EventRecorder .Event (node , corev1 .EventTypeNormal , "TaintAdopted" , message )
366+ }
367+
354368 default :
355369 log .Info ("No taint action needed" , "node" , node .Name , "rule" , rule .Name ,
356370 "shouldRemove" , shouldRemoveTaint , "hasTaint" , currentlyHasTaint )
@@ -598,7 +612,7 @@ func (r *RuleReadinessController) cleanupTaintsForRule(ctx context.Context, rule
598612 "rule" , rule .Name ,
599613 "taint" , rule .Spec .Taint .Key )
600614
601- if err := r .removeTaintBySpec (ctx , & node , rule .Spec .Taint ); err != nil {
615+ if err := r .removeTaintBySpec (ctx , & node , rule .Spec .Taint , rule . Name ); err != nil {
602616 errors = append (errors , fmt .Sprintf ("node %s: %v" , node .Name , err ))
603617 }
604618 }
@@ -650,7 +664,7 @@ func (r *RuleReadinessController) cleanupNodesAfterSelectorChange(ctx context.Co
650664 "rule" , newRule .Name ,
651665 "taint" , newRule .Spec .Taint .Key )
652666
653- if err := r .removeTaintBySpec (ctx , & node , newRule .Spec .Taint ); err != nil {
667+ if err := r .removeTaintBySpec (ctx , & node , newRule .Spec .Taint , newRule . Name ); err != nil {
654668 errors = append (errors , fmt .Sprintf ("node %s: %v" , node .Name , err ))
655669 }
656670 }
@@ -681,3 +695,14 @@ func (r *RuleReconciler) ensureFinalizer(ctx context.Context, rule *readinessv1a
681695 }
682696 return true , nil
683697}
698+
699+ // getPreviousNodeEvaluation retrieves the previous evaluation result for a specific node from the rule status.
700+ // It returns nil (if the node is evaluated for the first time) otherwsie, return the previously evaluated node data.
701+ func (r * RuleReadinessController ) getPreviousNodeEvaluation (rule * readinessv1alpha1.NodeReadinessRule , nodeName string ) * readinessv1alpha1.NodeEvaluation {
702+ for i := range rule .Status .NodeEvaluations {
703+ if rule .Status .NodeEvaluations [i ].NodeName == nodeName {
704+ return & rule .Status .NodeEvaluations [i ]
705+ }
706+ }
707+ return nil
708+ }
0 commit comments