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  }