k8s.io/kubernetes@v1.29.3/pkg/scheduler/testing/framework/fake_extender.go (about)

     1  /*
     2  Copyright 2020 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 framework
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	corev1helpers "k8s.io/component-helpers/scheduling/corev1"
    27  	"k8s.io/klog/v2"
    28  	extenderv1 "k8s.io/kube-scheduler/extender/v1"
    29  	"k8s.io/kubernetes/pkg/scheduler/framework"
    30  	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
    31  	"k8s.io/kubernetes/pkg/scheduler/util"
    32  )
    33  
    34  // FitPredicate is a function type which is used in fake extender.
    35  type FitPredicate func(pod *v1.Pod, node *v1.Node) *framework.Status
    36  
    37  // PriorityFunc is a function type which is used in fake extender.
    38  type PriorityFunc func(pod *v1.Pod, nodes []*v1.Node) (*framework.NodeScoreList, error)
    39  
    40  // PriorityConfig is used in fake extender to perform Prioritize function.
    41  type PriorityConfig struct {
    42  	Function PriorityFunc
    43  	Weight   int64
    44  }
    45  
    46  // ErrorPredicateExtender implements FitPredicate function to always return error status.
    47  func ErrorPredicateExtender(pod *v1.Pod, node *v1.Node) *framework.Status {
    48  	return framework.NewStatus(framework.Error, "some error")
    49  }
    50  
    51  // FalsePredicateExtender implements FitPredicate function to always return unschedulable status.
    52  func FalsePredicateExtender(pod *v1.Pod, node *v1.Node) *framework.Status {
    53  	return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("pod is unschedulable on the node %q", node.Name))
    54  }
    55  
    56  // TruePredicateExtender implements FitPredicate function to always return success status.
    57  func TruePredicateExtender(pod *v1.Pod, node *v1.Node) *framework.Status {
    58  	return framework.NewStatus(framework.Success)
    59  }
    60  
    61  // Node1PredicateExtender implements FitPredicate function to return true
    62  // when the given node's name is "node1"; otherwise return false.
    63  func Node1PredicateExtender(pod *v1.Pod, node *v1.Node) *framework.Status {
    64  	if node.Name == "node1" {
    65  		return framework.NewStatus(framework.Success)
    66  	}
    67  	return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("node %q is not allowed", node.Name))
    68  }
    69  
    70  // Node2PredicateExtender implements FitPredicate function to return true
    71  // when the given node's name is "node2"; otherwise return false.
    72  func Node2PredicateExtender(pod *v1.Pod, node *v1.Node) *framework.Status {
    73  	if node.Name == "node2" {
    74  		return framework.NewStatus(framework.Success)
    75  	}
    76  	return framework.NewStatus(framework.Unschedulable, fmt.Sprintf("node %q is not allowed", node.Name))
    77  }
    78  
    79  // ErrorPrioritizerExtender implements PriorityFunc function to always return error.
    80  func ErrorPrioritizerExtender(pod *v1.Pod, nodes []*v1.Node) (*framework.NodeScoreList, error) {
    81  	return &framework.NodeScoreList{}, fmt.Errorf("some error")
    82  }
    83  
    84  // Node1PrioritizerExtender implements PriorityFunc function to give score 10
    85  // if the given node's name is "node1"; otherwise score 1.
    86  func Node1PrioritizerExtender(pod *v1.Pod, nodes []*v1.Node) (*framework.NodeScoreList, error) {
    87  	result := framework.NodeScoreList{}
    88  	for _, node := range nodes {
    89  		score := 1
    90  		if node.Name == "node1" {
    91  			score = 10
    92  		}
    93  		result = append(result, framework.NodeScore{Name: node.Name, Score: int64(score)})
    94  	}
    95  	return &result, nil
    96  }
    97  
    98  // Node2PrioritizerExtender implements PriorityFunc function to give score 10
    99  // if the given node's name is "node2"; otherwise score 1.
   100  func Node2PrioritizerExtender(pod *v1.Pod, nodes []*v1.Node) (*framework.NodeScoreList, error) {
   101  	result := framework.NodeScoreList{}
   102  	for _, node := range nodes {
   103  		score := 1
   104  		if node.Name == "node2" {
   105  			score = 10
   106  		}
   107  		result = append(result, framework.NodeScore{Name: node.Name, Score: int64(score)})
   108  	}
   109  	return &result, nil
   110  }
   111  
   112  type node2PrioritizerPlugin struct{}
   113  
   114  // NewNode2PrioritizerPlugin returns a factory function to build node2PrioritizerPlugin.
   115  func NewNode2PrioritizerPlugin() frameworkruntime.PluginFactory {
   116  	return func(_ context.Context, _ runtime.Object, _ framework.Handle) (framework.Plugin, error) {
   117  		return &node2PrioritizerPlugin{}, nil
   118  	}
   119  }
   120  
   121  // Name returns name of the plugin.
   122  func (pl *node2PrioritizerPlugin) Name() string {
   123  	return "Node2Prioritizer"
   124  }
   125  
   126  // Score return score 100 if the given nodeName is "node2"; otherwise return score 10.
   127  func (pl *node2PrioritizerPlugin) Score(_ context.Context, _ *framework.CycleState, _ *v1.Pod, nodeName string) (int64, *framework.Status) {
   128  	score := 10
   129  	if nodeName == "node2" {
   130  		score = 100
   131  	}
   132  	return int64(score), nil
   133  }
   134  
   135  // ScoreExtensions returns nil.
   136  func (pl *node2PrioritizerPlugin) ScoreExtensions() framework.ScoreExtensions {
   137  	return nil
   138  }
   139  
   140  // FakeExtender is a data struct which implements the Extender interface.
   141  type FakeExtender struct {
   142  	// ExtenderName indicates this fake extender's name.
   143  	// Note that extender name should be unique.
   144  	ExtenderName     string
   145  	Predicates       []FitPredicate
   146  	Prioritizers     []PriorityConfig
   147  	Weight           int64
   148  	NodeCacheCapable bool
   149  	FilteredNodes    []*v1.Node
   150  	UnInterested     bool
   151  	Ignorable        bool
   152  
   153  	// Cached node information for fake extender
   154  	CachedNodeNameToInfo map[string]*framework.NodeInfo
   155  }
   156  
   157  const defaultFakeExtenderName = "defaultFakeExtender"
   158  
   159  // Name returns name of the extender.
   160  func (f *FakeExtender) Name() string {
   161  	if f.ExtenderName == "" {
   162  		// If ExtenderName is unset, use default name.
   163  		return defaultFakeExtenderName
   164  	}
   165  	return f.ExtenderName
   166  }
   167  
   168  // IsIgnorable returns a bool value indicating whether internal errors can be ignored.
   169  func (f *FakeExtender) IsIgnorable() bool {
   170  	return f.Ignorable
   171  }
   172  
   173  // SupportsPreemption returns true indicating the extender supports preemption.
   174  func (f *FakeExtender) SupportsPreemption() bool {
   175  	// Assume preempt verb is always defined.
   176  	return true
   177  }
   178  
   179  // ProcessPreemption implements the extender preempt function.
   180  func (f *FakeExtender) ProcessPreemption(
   181  	pod *v1.Pod,
   182  	nodeNameToVictims map[string]*extenderv1.Victims,
   183  	nodeInfos framework.NodeInfoLister,
   184  ) (map[string]*extenderv1.Victims, error) {
   185  	nodeNameToVictimsCopy := map[string]*extenderv1.Victims{}
   186  	// We don't want to change the original nodeNameToVictims
   187  	for k, v := range nodeNameToVictims {
   188  		// In real world implementation, extender's user should have their own way to get node object
   189  		// by name if needed (e.g. query kube-apiserver etc).
   190  		//
   191  		// For test purpose, we just use node from parameters directly.
   192  		nodeNameToVictimsCopy[k] = v
   193  	}
   194  
   195  	// If Extender.ProcessPreemption ever gets extended with a context parameter, then the logger should be retrieved from that.
   196  	// Now, in order not to modify the Extender interface, we get the logger from klog.TODO()
   197  	logger := klog.TODO()
   198  	for nodeName, victims := range nodeNameToVictimsCopy {
   199  		// Try to do preemption on extender side.
   200  		nodeInfo, _ := nodeInfos.Get(nodeName)
   201  		extenderVictimPods, extenderPDBViolations, fits, err := f.selectVictimsOnNodeByExtender(logger, pod, nodeInfo.Node())
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		// If it's unfit after extender's preemption, this node is unresolvable by preemption overall,
   206  		// let's remove it from potential preemption nodes.
   207  		if !fits {
   208  			delete(nodeNameToVictimsCopy, nodeName)
   209  		} else {
   210  			// Append new victims to original victims
   211  			nodeNameToVictimsCopy[nodeName].Pods = append(victims.Pods, extenderVictimPods...)
   212  			nodeNameToVictimsCopy[nodeName].NumPDBViolations = victims.NumPDBViolations + int64(extenderPDBViolations)
   213  		}
   214  	}
   215  	return nodeNameToVictimsCopy, nil
   216  }
   217  
   218  // selectVictimsOnNodeByExtender checks the given nodes->pods map with predicates on extender's side.
   219  // Returns:
   220  // 1. More victim pods (if any) amended by preemption phase of extender.
   221  // 2. Number of violating victim (used to calculate PDB).
   222  // 3. Fits or not after preemption phase on extender's side.
   223  func (f *FakeExtender) selectVictimsOnNodeByExtender(logger klog.Logger, pod *v1.Pod, node *v1.Node) ([]*v1.Pod, int, bool, error) {
   224  	// If a extender support preemption but have no cached node info, let's run filter to make sure
   225  	// default scheduler's decision still stand with given pod and node.
   226  	if !f.NodeCacheCapable {
   227  		err := f.runPredicate(pod, node)
   228  		if err.IsSuccess() {
   229  			return []*v1.Pod{}, 0, true, nil
   230  		} else if err.IsRejected() {
   231  			return nil, 0, false, nil
   232  		} else {
   233  			return nil, 0, false, err.AsError()
   234  		}
   235  	}
   236  
   237  	// Otherwise, as a extender support preemption and have cached node info, we will assume cachedNodeNameToInfo is available
   238  	// and get cached node info by given node name.
   239  	nodeInfoCopy := f.CachedNodeNameToInfo[node.GetName()].Snapshot()
   240  
   241  	var potentialVictims []*v1.Pod
   242  
   243  	removePod := func(rp *v1.Pod) error {
   244  		return nodeInfoCopy.RemovePod(logger, rp)
   245  	}
   246  	addPod := func(ap *v1.Pod) {
   247  		nodeInfoCopy.AddPod(ap)
   248  	}
   249  	// As the first step, remove all the lower priority pods from the node and
   250  	// check if the given pod can be scheduled.
   251  	podPriority := corev1helpers.PodPriority(pod)
   252  	for _, p := range nodeInfoCopy.Pods {
   253  		if corev1helpers.PodPriority(p.Pod) < podPriority {
   254  			potentialVictims = append(potentialVictims, p.Pod)
   255  			if err := removePod(p.Pod); err != nil {
   256  				return nil, 0, false, err
   257  			}
   258  		}
   259  	}
   260  	sort.Slice(potentialVictims, func(i, j int) bool { return util.MoreImportantPod(potentialVictims[i], potentialVictims[j]) })
   261  
   262  	// If the new pod does not fit after removing all the lower priority pods,
   263  	// we are almost done and this node is not suitable for preemption.
   264  	status := f.runPredicate(pod, nodeInfoCopy.Node())
   265  	if status.IsSuccess() {
   266  		// pass
   267  	} else if status.IsRejected() {
   268  		// does not fit
   269  		return nil, 0, false, nil
   270  	} else {
   271  		// internal errors
   272  		return nil, 0, false, status.AsError()
   273  	}
   274  
   275  	var victims []*v1.Pod
   276  
   277  	// TODO(harry): handle PDBs in the future.
   278  	numViolatingVictim := 0
   279  
   280  	reprievePod := func(p *v1.Pod) bool {
   281  		addPod(p)
   282  		status := f.runPredicate(pod, nodeInfoCopy.Node())
   283  		if !status.IsSuccess() {
   284  			if err := removePod(p); err != nil {
   285  				return false
   286  			}
   287  			victims = append(victims, p)
   288  		}
   289  		return status.IsSuccess()
   290  	}
   291  
   292  	// For now, assume all potential victims to be non-violating.
   293  	// Now we try to reprieve non-violating victims.
   294  	for _, p := range potentialVictims {
   295  		reprievePod(p)
   296  	}
   297  
   298  	return victims, numViolatingVictim, true, nil
   299  }
   300  
   301  // runPredicate run predicates of extender one by one for given pod and node.
   302  // Returns: fits or not.
   303  func (f *FakeExtender) runPredicate(pod *v1.Pod, node *v1.Node) *framework.Status {
   304  	for _, predicate := range f.Predicates {
   305  		status := predicate(pod, node)
   306  		if !status.IsSuccess() {
   307  			return status
   308  		}
   309  	}
   310  	return framework.NewStatus(framework.Success)
   311  }
   312  
   313  // Filter implements the extender Filter function.
   314  func (f *FakeExtender) Filter(pod *v1.Pod, nodes []*v1.Node) ([]*v1.Node, extenderv1.FailedNodesMap, extenderv1.FailedNodesMap, error) {
   315  	var filtered []*v1.Node
   316  	failedNodesMap := extenderv1.FailedNodesMap{}
   317  	failedAndUnresolvableMap := extenderv1.FailedNodesMap{}
   318  	for _, node := range nodes {
   319  		status := f.runPredicate(pod, node)
   320  		if status.IsSuccess() {
   321  			filtered = append(filtered, node)
   322  		} else if status.Code() == framework.Unschedulable {
   323  			failedNodesMap[node.Name] = fmt.Sprintf("FakeExtender: node %q failed", node.Name)
   324  		} else if status.Code() == framework.UnschedulableAndUnresolvable {
   325  			failedAndUnresolvableMap[node.Name] = fmt.Sprintf("FakeExtender: node %q failed and unresolvable", node.Name)
   326  		} else {
   327  			return nil, nil, nil, status.AsError()
   328  		}
   329  	}
   330  
   331  	f.FilteredNodes = filtered
   332  	if f.NodeCacheCapable {
   333  		return filtered, failedNodesMap, failedAndUnresolvableMap, nil
   334  	}
   335  	return filtered, failedNodesMap, failedAndUnresolvableMap, nil
   336  }
   337  
   338  // Prioritize implements the extender Prioritize function.
   339  func (f *FakeExtender) Prioritize(pod *v1.Pod, nodes []*v1.Node) (*extenderv1.HostPriorityList, int64, error) {
   340  	result := extenderv1.HostPriorityList{}
   341  	combinedScores := map[string]int64{}
   342  	for _, prioritizer := range f.Prioritizers {
   343  		weight := prioritizer.Weight
   344  		if weight == 0 {
   345  			continue
   346  		}
   347  		priorityFunc := prioritizer.Function
   348  		prioritizedList, err := priorityFunc(pod, nodes)
   349  		if err != nil {
   350  			return &extenderv1.HostPriorityList{}, 0, err
   351  		}
   352  		for _, hostEntry := range *prioritizedList {
   353  			combinedScores[hostEntry.Name] += hostEntry.Score * weight
   354  		}
   355  	}
   356  	for host, score := range combinedScores {
   357  		result = append(result, extenderv1.HostPriority{Host: host, Score: score})
   358  	}
   359  	return &result, f.Weight, nil
   360  }
   361  
   362  // Bind implements the extender Bind function.
   363  func (f *FakeExtender) Bind(binding *v1.Binding) error {
   364  	if len(f.FilteredNodes) != 0 {
   365  		for _, node := range f.FilteredNodes {
   366  			if node.Name == binding.Target.Name {
   367  				f.FilteredNodes = nil
   368  				return nil
   369  			}
   370  		}
   371  		err := fmt.Errorf("Node %v not in filtered nodes %v", binding.Target.Name, f.FilteredNodes)
   372  		f.FilteredNodes = nil
   373  		return err
   374  	}
   375  	return nil
   376  }
   377  
   378  // IsBinder returns true indicating the extender implements the Binder function.
   379  func (f *FakeExtender) IsBinder() bool {
   380  	return true
   381  }
   382  
   383  // IsInterested returns a bool indicating whether this extender is interested in this Pod.
   384  func (f *FakeExtender) IsInterested(pod *v1.Pod) bool {
   385  	return !f.UnInterested
   386  }
   387  
   388  var _ framework.Extender = &FakeExtender{}