k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/tainttoleration/taint_toleration.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tainttoleration 18 19 import ( 20 "context" 21 "fmt" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/runtime" 25 v1helper "k8s.io/component-helpers/scheduling/corev1" 26 "k8s.io/klog/v2" 27 "k8s.io/kubernetes/pkg/scheduler/framework" 28 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/helper" 29 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" 30 "k8s.io/kubernetes/pkg/scheduler/util" 31 ) 32 33 // TaintToleration is a plugin that checks if a pod tolerates a node's taints. 34 type TaintToleration struct { 35 handle framework.Handle 36 } 37 38 var _ framework.FilterPlugin = &TaintToleration{} 39 var _ framework.PreScorePlugin = &TaintToleration{} 40 var _ framework.ScorePlugin = &TaintToleration{} 41 var _ framework.EnqueueExtensions = &TaintToleration{} 42 43 const ( 44 // Name is the name of the plugin used in the plugin registry and configurations. 45 Name = names.TaintToleration 46 // preScoreStateKey is the key in CycleState to TaintToleration pre-computed data for Scoring. 47 preScoreStateKey = "PreScore" + Name 48 // ErrReasonNotMatch is the Filter reason status when not matching. 49 ErrReasonNotMatch = "node(s) had taints that the pod didn't tolerate" 50 ) 51 52 // Name returns name of the plugin. It is used in logs, etc. 53 func (pl *TaintToleration) Name() string { 54 return Name 55 } 56 57 // EventsToRegister returns the possible events that may make a Pod 58 // failed by this plugin schedulable. 59 func (pl *TaintToleration) EventsToRegister() []framework.ClusterEventWithHint { 60 return []framework.ClusterEventWithHint{ 61 {Event: framework.ClusterEvent{Resource: framework.Node, ActionType: framework.Add | framework.Update}, QueueingHintFn: pl.isSchedulableAfterNodeChange}, 62 } 63 } 64 65 // isSchedulableAfterNodeChange is invoked for all node events reported by 66 // an informer. It checks whether that change made a previously unschedulable 67 // pod schedulable. 68 func (pl *TaintToleration) isSchedulableAfterNodeChange(logger klog.Logger, pod *v1.Pod, oldObj, newObj interface{}) (framework.QueueingHint, error) { 69 originalNode, modifiedNode, err := util.As[*v1.Node](oldObj, newObj) 70 if err != nil { 71 return framework.Queue, err 72 } 73 74 wasUntolerated := true 75 if originalNode != nil { 76 _, wasUntolerated = v1helper.FindMatchingUntoleratedTaint(originalNode.Spec.Taints, pod.Spec.Tolerations, helper.DoNotScheduleTaintsFilterFunc()) 77 } 78 79 _, isUntolerated := v1helper.FindMatchingUntoleratedTaint(modifiedNode.Spec.Taints, pod.Spec.Tolerations, helper.DoNotScheduleTaintsFilterFunc()) 80 81 if wasUntolerated && !isUntolerated { 82 logger.V(5).Info("node was created or updated, and this may make the Pod rejected by TaintToleration plugin in the previous scheduling cycle schedulable", "pod", klog.KObj(pod), "node", klog.KObj(modifiedNode)) 83 return framework.Queue, nil 84 } 85 86 logger.V(5).Info("node was created or updated, but it doesn't change the TaintToleration plugin's decision", "pod", klog.KObj(pod), "node", klog.KObj(modifiedNode)) 87 return framework.QueueSkip, nil 88 } 89 90 // Filter invoked at the filter extension point. 91 func (pl *TaintToleration) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { 92 node := nodeInfo.Node() 93 94 taint, isUntolerated := v1helper.FindMatchingUntoleratedTaint(node.Spec.Taints, pod.Spec.Tolerations, helper.DoNotScheduleTaintsFilterFunc()) 95 if !isUntolerated { 96 return nil 97 } 98 99 errReason := fmt.Sprintf("node(s) had untolerated taint {%s: %s}", taint.Key, taint.Value) 100 return framework.NewStatus(framework.UnschedulableAndUnresolvable, errReason) 101 } 102 103 // preScoreState computed at PreScore and used at Score. 104 type preScoreState struct { 105 tolerationsPreferNoSchedule []v1.Toleration 106 } 107 108 // Clone implements the mandatory Clone interface. We don't really copy the data since 109 // there is no need for that. 110 func (s *preScoreState) Clone() framework.StateData { 111 return s 112 } 113 114 // getAllTolerationEffectPreferNoSchedule gets the list of all Tolerations with Effect PreferNoSchedule or with no effect. 115 func getAllTolerationPreferNoSchedule(tolerations []v1.Toleration) (tolerationList []v1.Toleration) { 116 for _, toleration := range tolerations { 117 // Empty effect means all effects which includes PreferNoSchedule, so we need to collect it as well. 118 if len(toleration.Effect) == 0 || toleration.Effect == v1.TaintEffectPreferNoSchedule { 119 tolerationList = append(tolerationList, toleration) 120 } 121 } 122 return 123 } 124 125 // PreScore builds and writes cycle state used by Score and NormalizeScore. 126 func (pl *TaintToleration) PreScore(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodes []*framework.NodeInfo) *framework.Status { 127 if len(nodes) == 0 { 128 return nil 129 } 130 tolerationsPreferNoSchedule := getAllTolerationPreferNoSchedule(pod.Spec.Tolerations) 131 state := &preScoreState{ 132 tolerationsPreferNoSchedule: tolerationsPreferNoSchedule, 133 } 134 cycleState.Write(preScoreStateKey, state) 135 return nil 136 } 137 138 func getPreScoreState(cycleState *framework.CycleState) (*preScoreState, error) { 139 c, err := cycleState.Read(preScoreStateKey) 140 if err != nil { 141 return nil, fmt.Errorf("failed to read %q from cycleState: %w", preScoreStateKey, err) 142 } 143 144 s, ok := c.(*preScoreState) 145 if !ok { 146 return nil, fmt.Errorf("%+v convert to tainttoleration.preScoreState error", c) 147 } 148 return s, nil 149 } 150 151 // CountIntolerableTaintsPreferNoSchedule gives the count of intolerable taints of a pod with effect PreferNoSchedule 152 func countIntolerableTaintsPreferNoSchedule(taints []v1.Taint, tolerations []v1.Toleration) (intolerableTaints int) { 153 for _, taint := range taints { 154 // check only on taints that have effect PreferNoSchedule 155 if taint.Effect != v1.TaintEffectPreferNoSchedule { 156 continue 157 } 158 159 if !v1helper.TolerationsTolerateTaint(tolerations, &taint) { 160 intolerableTaints++ 161 } 162 } 163 return 164 } 165 166 // Score invoked at the Score extension point. 167 func (pl *TaintToleration) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { 168 nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) 169 if err != nil { 170 return 0, framework.AsStatus(fmt.Errorf("getting node %q from Snapshot: %w", nodeName, err)) 171 } 172 node := nodeInfo.Node() 173 174 s, err := getPreScoreState(state) 175 if err != nil { 176 return 0, framework.AsStatus(err) 177 } 178 179 score := int64(countIntolerableTaintsPreferNoSchedule(node.Spec.Taints, s.tolerationsPreferNoSchedule)) 180 return score, nil 181 } 182 183 // NormalizeScore invoked after scoring all nodes. 184 func (pl *TaintToleration) NormalizeScore(ctx context.Context, _ *framework.CycleState, pod *v1.Pod, scores framework.NodeScoreList) *framework.Status { 185 return helper.DefaultNormalizeScore(framework.MaxNodeScore, true, scores) 186 } 187 188 // ScoreExtensions of the Score plugin. 189 func (pl *TaintToleration) ScoreExtensions() framework.ScoreExtensions { 190 return pl 191 } 192 193 // New initializes a new plugin and returns it. 194 func New(_ context.Context, _ runtime.Object, h framework.Handle) (framework.Plugin, error) { 195 return &TaintToleration{handle: h}, nil 196 }