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{}