@@ -27,6 +27,7 @@ import (
2727 v1 "k8s.io/api/core/v1"
2828 "k8s.io/apimachinery/pkg/api/errors"
2929 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+ "k8s.io/apimachinery/pkg/types"
3031 "k8s.io/apimachinery/pkg/util/wait"
3132 "k8s.io/client-go/kubernetes"
3233 "k8s.io/client-go/rest"
@@ -57,6 +58,7 @@ type FaultQuarantineClient struct {
5758 cordonedReasonLabelKey string
5859 uncordonedReasonLabelKey string
5960 operationMutex sync.Map // map[string]*sync.Mutex for per-node locking
61+ lastWrittenNodeVersion sync.Map // map[string]string
6062}
6163
6264// NewFaultQuarantineClient constructs a FaultQuarantineClient using the
@@ -171,26 +173,82 @@ func (c *FaultQuarantineClient) UpdateNode(ctx context.Context, nodeName string,
171173 }
172174
173175 return retry .OnError (backoff , isRetryableError , func () error {
174- node , err := c .Clientset . CoreV1 (). Nodes (). Get ( ctx , nodeName , metav1. GetOptions {} )
176+ current , err := c .nodeForPatch ( ctx , nodeName )
175177 if err != nil {
176178 return err
177179 }
178180
179- if err := updateFn (node ); err != nil {
181+ desired := current .DeepCopy ()
182+ if err := updateFn (desired ); err != nil {
180183 return err
181184 }
182185
183- _ , err = c . Clientset . CoreV1 (). Nodes (). Update ( ctx , node , metav1. UpdateOptions {} )
186+ patch , err := kubeclient . NodeMergePatch ( current , desired )
184187 if err != nil {
185188 return err
186189 }
187190
188- slog .Debug ("Updated node" , "node" , nodeName )
191+ if patch == nil {
192+ return nil
193+ }
194+
195+ updated , err := c .Clientset .CoreV1 ().Nodes ().Patch (
196+ ctx ,
197+ nodeName ,
198+ types .MergePatchType ,
199+ patch ,
200+ metav1.PatchOptions {},
201+ )
202+ if err != nil {
203+ return err
204+ }
205+
206+ c .lastWrittenNodeVersion .Store (nodeName , updated .ResourceVersion )
207+ slog .Debug ("Patched node" , "node" , nodeName )
189208
190209 return nil
191210 })
192211}
193212
213+ func (c * FaultQuarantineClient ) nodeForPatch (ctx context.Context , nodeName string ) (* v1.Node , error ) {
214+ if node , ready := c .cachedNodeForPatch (nodeName ); ready {
215+ return node , nil
216+ }
217+
218+ // The informer may not be running in tests or may not have observed our previous
219+ // write yet. A live read preserves consecutive updates to the same node.
220+ node , err := c .Clientset .CoreV1 ().Nodes ().Get (ctx , nodeName , metav1.GetOptions {})
221+ if err != nil {
222+ return nil , err
223+ }
224+
225+ c .lastWrittenNodeVersion .Delete (nodeName )
226+
227+ return node , nil
228+ }
229+
230+ func (c * FaultQuarantineClient ) cachedNodeForPatch (nodeName string ) (* v1.Node , bool ) {
231+ if c .NodeInformer == nil || ! c .NodeInformer .HasSynced () {
232+ return nil , false
233+ }
234+
235+ node , err := c .NodeInformer .GetNode (nodeName )
236+ if err != nil {
237+ return nil , false
238+ }
239+
240+ lastWrittenVersion , pending := c .lastWrittenNodeVersion .Load (nodeName )
241+ if pending && node .ResourceVersion != lastWrittenVersion .(string ) {
242+ return nil , false
243+ }
244+
245+ if pending {
246+ c .lastWrittenNodeVersion .Delete (nodeName )
247+ }
248+
249+ return node .DeepCopy (), true
250+ }
251+
194252func isRetryableError (err error ) bool {
195253 if errors .IsConflict (err ) {
196254 return true
@@ -376,29 +434,25 @@ func (c *FaultQuarantineClient) applyTaints(
376434 return nil
377435 }
378436
379- existingTaints := make (map [config.Taint ]v1. Taint )
437+ existingTaints := make (map [config.Taint ]struct {} )
380438 for _ , taint := range node .Spec .Taints {
381- existingTaints [config.Taint {Key : taint .Key , Value : taint .Value , Effect : string (taint .Effect )}] = taint
439+ existingTaints [config.Taint {Key : taint .Key , Value : taint .Value , Effect : string (taint .Effect )}] = struct {}{}
382440 }
383441
384442 for _ , taintConfig := range taints {
385443 key := config.Taint {Key : taintConfig .Key , Value : taintConfig .Value , Effect : string (taintConfig .Effect )}
386444
387445 if _ , exists := existingTaints [key ]; ! exists {
388446 slog .InfoContext (ctx , "Tainting node" , "node" , nodename , "taintConfig" , taintConfig )
389- existingTaints [ key ] = v1.Taint {
447+ node . Spec . Taints = append ( node . Spec . Taints , v1.Taint {
390448 Key : taintConfig .Key ,
391449 Value : taintConfig .Value ,
392450 Effect : v1 .TaintEffect (taintConfig .Effect ),
393- }
451+ })
452+ existingTaints [key ] = struct {}{}
394453 }
395454 }
396455
397- node .Spec .Taints = []v1.Taint {}
398- for _ , taint := range existingTaints {
399- node .Spec .Taints = append (node .Spec .Taints , taint )
400- }
401-
402456 return nil
403457}
404458
0 commit comments