diff --git a/internal/controller/falcon_node/falconnodesensor_controller.go b/internal/controller/falcon_node/falconnodesensor_controller.go index c6bd37ae..9089fea4 100644 --- a/internal/controller/falcon_node/falconnodesensor_controller.go +++ b/internal/controller/falcon_node/falconnodesensor_controller.go @@ -213,7 +213,10 @@ func (r *FalconNodeSensorReconciler) Reconcile(ctx context.Context, req ctrl.Req } } - _ = updateDaemonSetTolerations(ds, nodesensor, logger) + _, err = r.updateDaemonSetTolerations(ctx, ds, nodesensor, logger) + if err != nil { + return ctrl.Result{}, err + } err = r.Create(ctx, ds) if err != nil { @@ -249,7 +252,6 @@ func (r *FalconNodeSensorReconciler) Reconcile(ctx context.Context, req ctrl.Req // Objects to check for updates to re-spin pods imgUpdate := updateDaemonSetImages(dsUpdate, image, nodesensor, logger) - tolsUpdate := updateDaemonSetTolerations(dsUpdate, nodesensor, logger) affUpdate := updateDaemonSetAffinity(dsUpdate, dsTarget, nodesensor, logger) containerVolUpdate := updateDaemonSetContainerVolumes(dsUpdate, dsTarget, logger) volumeUpdates := updateDaemonSetVolumes(dsUpdate, dsTarget, logger) @@ -259,6 +261,10 @@ func (r *FalconNodeSensorReconciler) Reconcile(ctx context.Context, req ctrl.Req capabilities := updateDaemonSetCapabilities(dsUpdate, dsTarget, logger) initArgs := updateDaemonSetInitArgs(dsUpdate, dsTarget, logger) updated = updateDaemonSetContainerProxy(dsUpdate, nodesensor, logger) + tolsUpdate, err := r.updateDaemonSetTolerations(ctx, dsUpdate, nodesensor, logger) + if err != nil { + return ctrl.Result{}, err + } // Update the daemonset and re-spin pods with changes if imgUpdate || tolsUpdate || affUpdate || containerVolUpdate || volumeUpdates || resources || pc || capabilities || initArgs || initResources || updated { @@ -567,15 +573,22 @@ func updateDaemonSetContainerProxy(ds *appsv1.DaemonSet, nodesensor *falconv1alp } // If an update is needed, this will update the tolerations from the given DaemonSet -func updateDaemonSetTolerations(ds *appsv1.DaemonSet, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) bool { +func (r *FalconNodeSensorReconciler) updateDaemonSetTolerations(ctx context.Context, ds *appsv1.DaemonSet, nodesensor *falconv1alpha1.FalconNodeSensor, logger logr.Logger) (bool, error) { tolerations := &ds.Spec.Template.Spec.Tolerations origTolerations := nodesensor.Spec.Node.Tolerations tolerationsUpdate := !equality.Semantic.DeepEqual(*tolerations, origTolerations) if tolerationsUpdate { logger.Info("Updating FalconNodeSensor DaemonSet Tolerations") - *tolerations = origTolerations + mergedTolerations := k8s_utils.MergeTolerations(*tolerations, origTolerations) + *tolerations = mergedTolerations + nodesensor.Spec.Node.Tolerations = mergedTolerations + + if err := r.Update(ctx, nodesensor); err != nil { + logger.Error(err, "Failed to update FalconNodeSensor Tolerations") + return false, err + } } - return tolerationsUpdate + return tolerationsUpdate, nil } // If an update is needed, this will update the affinity from the given DaemonSet diff --git a/pkg/k8s_utils/tolerations.go b/pkg/k8s_utils/tolerations.go new file mode 100644 index 00000000..a16391f8 --- /dev/null +++ b/pkg/k8s_utils/tolerations.go @@ -0,0 +1,85 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package k8s_utils + +/* Code from kubernetes project for merging tolerations */ +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" +) + +func MergeTolerations(first, second []corev1.Toleration) []corev1.Toleration { + all := append(first, second...) + var merged []corev1.Toleration + +next: + for i, t := range all { + for _, t2 := range merged { + if isSuperset(t2, t) { + continue next // t is redundant; ignore it + } + } + if i+1 < len(all) { + for _, t2 := range all[i+1:] { + // If the tolerations are equal, prefer the first. + if !equality.Semantic.DeepEqual(&t, &t2) && isSuperset(t2, t) { // #nosec + continue next // t is redundant; ignore it + } + } + } + merged = append(merged, t) + } + + return merged +} + +// isSuperset checks whether ss tolerates a superset of t. +func isSuperset(ss, t corev1.Toleration) bool { + if equality.Semantic.DeepEqual(&t, &ss) { + return true + } + + if t.Key != ss.Key && + // An empty key with Exists operator means match all keys & values. + (ss.Key != "" || ss.Operator != corev1.TolerationOpExists) { + return false + } + + // An empty effect means match all effects. + if t.Effect != ss.Effect && ss.Effect != "" { + return false + } + + if ss.Effect == corev1.TaintEffectNoExecute { + if ss.TolerationSeconds != nil { + if t.TolerationSeconds == nil || + *t.TolerationSeconds > *ss.TolerationSeconds { + return false + } + } + } + + switch ss.Operator { + case corev1.TolerationOpEqual, "": // empty operator means Equal + return t.Operator == corev1.TolerationOpEqual && t.Value == ss.Value + case corev1.TolerationOpExists: + return true + default: + return false + } +} + +/* End code from kubernetes project for merging tolerations */