/* Copyright 2019 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. */ /* NOTE: the contents of this file has been copied from k8s.io/kubernetes/pkg/controller and k8s.io/kubernetes/pkg/util/taints. The reason for duplicating this code is to remove dependencies to k8s.io/kubernetes in all the cloud providers. Once k8s.io/kubernetes/pkg/util/taints is moved to an external repository, this file should be removed and replaced with that one. */ package helpers import ( "context" "encoding/json" "fmt" "time" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" clientretry "k8s.io/client-go/util/retry" ) var updateTaintBackoff = wait.Backoff{ Steps: 5, Duration: 100 * time.Millisecond, Jitter: 1.0, } // AddOrUpdateTaintOnNode add taints to the node. If taint was added into node, it'll issue API calls // to update nodes; otherwise, no API calls. Return error if any. func AddOrUpdateTaintOnNode(c clientset.Interface, nodeName string, taints ...*v1.Taint) error { if len(taints) == 0 { return nil } firstTry := true return clientretry.RetryOnConflict(updateTaintBackoff, func() error { var err error var oldNode *v1.Node // First we try getting node from the API server cache, as it's cheaper. If it fails // we get it from etcd to be sure to have fresh data. if firstTry { oldNode, err = c.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{ResourceVersion: "0"}) firstTry = false } else { oldNode, err = c.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) } if err != nil { return err } var newNode *v1.Node oldNodeCopy := oldNode updated := false for _, taint := range taints { curNewNode, ok, err := addOrUpdateTaint(oldNodeCopy, taint) if err != nil { return fmt.Errorf("failed to update taint of node") } updated = updated || ok newNode = curNewNode oldNodeCopy = curNewNode } if !updated { return nil } return PatchNodeTaints(c, nodeName, oldNode, newNode) }) } // PatchNodeTaints patches node's taints. func PatchNodeTaints(c clientset.Interface, nodeName string, oldNode *v1.Node, newNode *v1.Node) error { oldData, err := json.Marshal(oldNode) if err != nil { return fmt.Errorf("failed to marshal old node %#v for node %q: %v", oldNode, nodeName, err) } newTaints := newNode.Spec.Taints newNodeClone := oldNode.DeepCopy() newNodeClone.Spec.Taints = newTaints newData, err := json.Marshal(newNodeClone) if err != nil { return fmt.Errorf("failed to marshal new node %#v for node %q: %v", newNodeClone, nodeName, err) } patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{}) if err != nil { return fmt.Errorf("failed to create patch for node %q: %v", nodeName, err) } _, err = c.CoreV1().Nodes().Patch(context.TODO(), nodeName, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}) return err } // addOrUpdateTaint tries to add a taint to annotations list. Returns a new copy of updated Node and true if something was updated // false otherwise. func addOrUpdateTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) { newNode := node.DeepCopy() nodeTaints := newNode.Spec.Taints var newTaints []v1.Taint updated := false for i := range nodeTaints { if taint.MatchTaint(&nodeTaints[i]) { if equality.Semantic.DeepEqual(*taint, nodeTaints[i]) { return newNode, false, nil } newTaints = append(newTaints, *taint) updated = true continue } newTaints = append(newTaints, nodeTaints[i]) } if !updated { newTaints = append(newTaints, *taint) } newNode.Spec.Taints = newTaints return newNode, true, nil }