Skip to content

Commit d93ad59

Browse files
committed
taint propagation: implement propagation in machine controller
1 parent c0dd95a commit d93ad59

File tree

1 file changed

+109
-2
lines changed

1 file changed

+109
-2
lines changed

internal/controllers/machine/machine_controller_noderef.go

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ import (
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3030
"k8s.io/apimachinery/pkg/runtime/schema"
31+
"k8s.io/apimachinery/pkg/util/sets"
3132
"k8s.io/klog/v2"
33+
"k8s.io/utils/ptr"
3234
ctrl "sigs.k8s.io/controller-runtime"
3335
"sigs.k8s.io/controller-runtime/pkg/client"
3436

3537
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
3638
"sigs.k8s.io/cluster-api/api/core/v1beta2/index"
39+
"sigs.k8s.io/cluster-api/feature"
3740
"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
3841
"sigs.k8s.io/cluster-api/internal/util/taints"
3942
"sigs.k8s.io/cluster-api/util"
@@ -335,6 +338,15 @@ func (r *Reconciler) patchNode(ctx context.Context, remoteClient client.Client,
335338
// Drop the NodeUninitializedTaint taint on the node given that we are reconciling labels.
336339
hasTaintChanges := taints.RemoveNodeTaint(newNode, clusterv1.NodeUninitializedTaint)
337340

341+
// Propagate taints set on the Machine to the Node.
342+
var propagateTaintsChanges bool
343+
if feature.Gates.Enabled(feature.MachineTaintPropagation) {
344+
var err error
345+
if propagateTaintsChanges, err = propagateMachineTaintsToNode(newNode, m.Spec.Taints); err != nil {
346+
return errors.Wrapf(err, "failed to propagate machine taints to node %s", klog.KRef("", node.Name))
347+
}
348+
}
349+
338350
// Set Taint to a node in an old MachineSet and unset Taint from a node in a new MachineSet
339351
isOutdated, notFound, err := shouldNodeHaveOutdatedTaint(ctx, r.Client, m)
340352
if err != nil {
@@ -352,11 +364,106 @@ func (r *Reconciler) patchNode(ctx context.Context, remoteClient client.Client,
352364
}
353365
}
354366

355-
if !hasAnnotationChanges && !hasLabelChanges && !hasTaintChanges {
367+
if !hasAnnotationChanges && !hasLabelChanges && !hasTaintChanges && !propagateTaintsChanges {
356368
return nil
357369
}
358370

359-
return remoteClient.Patch(ctx, newNode, client.StrategicMergeFrom(node))
371+
var mergeOptions []client.MergeFromOption
372+
if feature.Gates.Enabled(feature.MachineTaintPropagation) {
373+
mergeOptions = append(mergeOptions, client.MergeFromWithOptimisticLock{})
374+
}
375+
376+
return remoteClient.Patch(ctx, newNode, client.StrategicMergeFrom(node, mergeOptions...))
377+
}
378+
379+
// propagateMachineTaintsToNode handles propagation of taints defined on a machine to a node.
380+
// It makes use of the annotation clusterv1.TaintsFromMachineAnnotation to track which taints are owned by the controller.
381+
// OnInitialization taints are only added to the node if the tracking annotation has not been set yet.
382+
func propagateMachineTaintsToNode(node *corev1.Node, machineTaints []clusterv1.MachineTaint) (bool, error) {
383+
changed := false
384+
385+
// Get the value of the tracking annotation. If it is not set at all we also have to add the OnInitialization taints.
386+
oldTaintsAnnotation, nodeTaintsInitialized := node.Annotations[clusterv1.TaintsFromMachineAnnotation]
387+
388+
// ownedTaints contains all Always taints that the controller is owning.
389+
ownedTaints := unmarshalMachineTaintsAnnotation(oldTaintsAnnotation)
390+
391+
// newOwnedTaints will contain all Always taints from the current machine's spec.
392+
newOwnedTaints := sets.New[string]()
393+
onInitializationTaints := sets.New[string]()
394+
395+
for _, taint := range machineTaints {
396+
// Collect Always and OnInitialization taints to identify taints to delete.
397+
// Separating Always taints so the tracking annotation can be updated accordingly.
398+
switch taint.Propagation {
399+
case clusterv1.TaintPropagationAlways:
400+
newOwnedTaints.Insert(fmt.Sprintf("%s:%s", taint.Key, taint.Effect))
401+
case clusterv1.TaintPropagationOnInitialization:
402+
onInitializationTaints.Insert(fmt.Sprintf("%s:%s", taint.Key, taint.Effect))
403+
}
404+
405+
// Only add OnInitialization taints if the tracking annotation has not been set yet.
406+
if taint.Propagation == clusterv1.TaintPropagationOnInitialization && nodeTaintsInitialized {
407+
continue
408+
}
409+
410+
// Ensure the taint is set on the node and has the correct value.
411+
if changedTaints := taints.EnsureNodeTaint(node, convertMachineTaintToCoreV1Taint(taint)); changedTaints {
412+
changed = true
413+
}
414+
}
415+
416+
// Calculate ownedTaints - newOwnedTaints to identify old taints which need to be deleted from the node.
417+
taintsToDelete := ownedTaints.Difference(newOwnedTaints).Difference(onInitializationTaints)
418+
419+
// Remove all identified taints from the node.
420+
for taintToDelete := range taintsToDelete {
421+
splitted := strings.Split(taintToDelete, ":")
422+
if len(splitted) != 2 {
423+
return changed, fmt.Errorf("invalid taint format: %q", taintToDelete)
424+
}
425+
426+
if removedTaint := taints.RemoveNodeTaint(node, corev1.Taint{Key: splitted[0], Effect: corev1.TaintEffect(splitted[1])}); removedTaint {
427+
changed = true
428+
}
429+
}
430+
431+
// Update the tracking annotation with newOwnedTaints..
432+
if newTaintsAnnotation := marshalMachineTaintsAnnotation(newOwnedTaints); !nodeTaintsInitialized || newTaintsAnnotation != oldTaintsAnnotation {
433+
if node.Annotations == nil {
434+
node.Annotations = map[string]string{}
435+
}
436+
node.Annotations[clusterv1.TaintsFromMachineAnnotation] = newTaintsAnnotation
437+
changed = true
438+
}
439+
440+
return changed, nil
441+
}
442+
443+
// marshalMachineTaintsAnnotation marshals the tracking annotation value.
444+
func marshalMachineTaintsAnnotation(ownedTaints sets.Set[string]) string {
445+
taints := ownedTaints.UnsortedList()
446+
slices.Sort(taints)
447+
448+
return strings.Join(taints, ",")
449+
}
450+
451+
// unmarshalMachineTaintsAnnotation unmarshals the tracking annotation value.
452+
func unmarshalMachineTaintsAnnotation(annotationValue string) sets.Set[string] {
453+
if annotationValue == "" {
454+
return nil
455+
}
456+
457+
return sets.New(strings.Split(annotationValue, ",")...)
458+
}
459+
460+
// convertMachineTaintToCoreV1Taint converts a MachineTaint to a corev1.Taint.
461+
func convertMachineTaintToCoreV1Taint(machineTaint clusterv1.MachineTaint) corev1.Taint {
462+
return corev1.Taint{
463+
Key: machineTaint.Key,
464+
Value: ptr.Deref(machineTaint.Value, ""),
465+
Effect: machineTaint.Effect,
466+
}
360467
}
361468

362469
// shouldNodeHaveOutdatedTaint tries to compare the revision of the owning MachineSet to the MachineDeployment.

0 commit comments

Comments
 (0)