volcano.sh/volcano@v1.9.0/pkg/scheduler/util/scheduler_helper.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 util 18 19 import ( 20 "context" 21 "fmt" 22 "math" 23 "math/rand" 24 "sort" 25 "sync" 26 27 v1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/resource" 29 "k8s.io/client-go/util/workqueue" 30 "k8s.io/klog/v2" 31 k8sframework "k8s.io/kubernetes/pkg/scheduler/framework" 32 33 "volcano.sh/volcano/cmd/scheduler/app/options" 34 "volcano.sh/volcano/pkg/scheduler/api" 35 ) 36 37 const baselinePercentageOfNodesToFind = 50 38 39 var lastProcessedNodeIndex int 40 41 // CalculateNumOfFeasibleNodesToFind returns the number of feasible nodes that once found, 42 // the scheduler stops its search for more feasible nodes. 43 func CalculateNumOfFeasibleNodesToFind(numAllNodes int32) (numNodes int32) { 44 opts := options.ServerOpts 45 if numAllNodes <= opts.MinNodesToFind || opts.PercentageOfNodesToFind >= 100 { 46 return numAllNodes 47 } 48 49 adaptivePercentage := opts.PercentageOfNodesToFind 50 if adaptivePercentage <= 0 { 51 adaptivePercentage = baselinePercentageOfNodesToFind - numAllNodes/125 52 if adaptivePercentage < opts.MinPercentageOfNodesToFind { 53 adaptivePercentage = opts.MinPercentageOfNodesToFind 54 } 55 } 56 57 numNodes = numAllNodes * adaptivePercentage / 100 58 if numNodes < opts.MinNodesToFind { 59 numNodes = opts.MinNodesToFind 60 } 61 return numNodes 62 } 63 64 // PrioritizeNodes returns a map whose key is node's score and value are corresponding nodes 65 func PrioritizeNodes(task *api.TaskInfo, nodes []*api.NodeInfo, batchFn api.BatchNodeOrderFn, mapFn api.NodeOrderMapFn, reduceFn api.NodeOrderReduceFn) map[float64][]*api.NodeInfo { 66 pluginNodeScoreMap := map[string]k8sframework.NodeScoreList{} 67 nodeOrderScoreMap := map[string]float64{} 68 nodeScores := map[float64][]*api.NodeInfo{} 69 var workerLock sync.Mutex 70 scoreNode := func(index int) { 71 node := nodes[index] 72 mapScores, orderScore, err := mapFn(task, node) 73 if err != nil { 74 klog.Errorf("Error in Calculating Priority for the node:%v", err) 75 return 76 } 77 78 workerLock.Lock() 79 for plugin, score := range mapScores { 80 nodeScoreList, ok := pluginNodeScoreMap[plugin] 81 if !ok { 82 nodeScoreList = k8sframework.NodeScoreList{} 83 } 84 hp := k8sframework.NodeScore{} 85 hp.Name = node.Name 86 hp.Score = int64(math.Floor(score)) 87 pluginNodeScoreMap[plugin] = append(nodeScoreList, hp) 88 } 89 nodeOrderScoreMap[node.Name] = orderScore 90 workerLock.Unlock() 91 } 92 workqueue.ParallelizeUntil(context.TODO(), 16, len(nodes), scoreNode) 93 reduceScores, err := reduceFn(task, pluginNodeScoreMap) 94 if err != nil { 95 klog.Errorf("Error in Calculating Priority for the node:%v", err) 96 return nodeScores 97 } 98 99 batchNodeScore, err := batchFn(task, nodes) 100 if err != nil { 101 klog.Errorf("Error in Calculating batch Priority for the node, err %v", err) 102 return nodeScores 103 } 104 105 nodeScoreMap := map[string]float64{} 106 for _, node := range nodes { 107 // If no plugin is applied to this node, the default is 0.0 108 score := 0.0 109 if reduceScore, ok := reduceScores[node.Name]; ok { 110 score += reduceScore 111 } 112 if orderScore, ok := nodeOrderScoreMap[node.Name]; ok { 113 score += orderScore 114 } 115 if batchScore, ok := batchNodeScore[node.Name]; ok { 116 score += batchScore 117 } 118 nodeScores[score] = append(nodeScores[score], node) 119 120 if klog.V(5).Enabled() { 121 nodeScoreMap[node.Name] = score 122 } 123 } 124 125 klog.V(5).Infof("Prioritize nodeScoreMap for task<%s/%s> is: %v", task.Namespace, task.Name, nodeScoreMap) 126 return nodeScores 127 } 128 129 // SortNodes returns nodes by order of score 130 func SortNodes(nodeScores map[float64][]*api.NodeInfo) []*api.NodeInfo { 131 var nodesInorder []*api.NodeInfo 132 var keys []float64 133 for key := range nodeScores { 134 keys = append(keys, key) 135 } 136 sort.Sort(sort.Reverse(sort.Float64Slice(keys))) 137 for _, key := range keys { 138 nodes := nodeScores[key] 139 nodesInorder = append(nodesInorder, nodes...) 140 } 141 return nodesInorder 142 } 143 144 // SelectBestNode returns best node whose score is highest, pick one randomly if there are many nodes with same score. 145 func SelectBestNode(nodeScores map[float64][]*api.NodeInfo) *api.NodeInfo { 146 var bestNodes []*api.NodeInfo 147 maxScore := -1.0 148 for score, nodes := range nodeScores { 149 if score > maxScore { 150 maxScore = score 151 bestNodes = nodes 152 } 153 } 154 155 if len(bestNodes) == 0 { 156 return nil 157 } 158 159 return bestNodes[rand.Intn(len(bestNodes))] 160 } 161 162 // GetNodeList returns values of the map 'nodes' 163 func GetNodeList(nodes map[string]*api.NodeInfo, nodeList []string) []*api.NodeInfo { 164 result := make([]*api.NodeInfo, 0, len(nodeList)) 165 for _, nodename := range nodeList { 166 if ni, ok := nodes[nodename]; ok { 167 result = append(result, ni) 168 } 169 } 170 return result 171 } 172 173 // ValidateVictims returns an error if the resources of the victims can't satisfy the preemptor 174 func ValidateVictims(preemptor *api.TaskInfo, node *api.NodeInfo, victims []*api.TaskInfo) error { 175 // Victims should not be judged to be empty here. 176 // It is possible to complete the scheduling of the preemptor without evicting the task. 177 // In the first round, a large task (CPU: 8) is expelled, and a small task is scheduled (CPU: 2) 178 // When the following rounds of victims are empty, it is still allowed to schedule small tasks (CPU: 2) 179 futureIdle := node.FutureIdle() 180 for _, victim := range victims { 181 futureIdle.Add(victim.Resreq) 182 } 183 // Every resource of the preemptor needs to be less or equal than corresponding 184 // idle resource after preemption. 185 if !preemptor.InitResreq.LessEqual(futureIdle, api.Zero) { 186 return fmt.Errorf("not enough resources: requested <%v>, but future idle <%v>", 187 preemptor.InitResreq, futureIdle) 188 } 189 return nil 190 } 191 192 // GetMinInt return minimum int from vals 193 func GetMinInt(vals ...int) int { 194 if len(vals) == 0 { 195 return 0 196 } 197 198 min := vals[0] 199 for _, val := range vals { 200 if val <= min { 201 min = val 202 } 203 } 204 return min 205 } 206 207 // ConvertRes2ResList convert resource type from api.Resource in scheduler to v1.ResourceList in yaml 208 func ConvertRes2ResList(res *api.Resource) v1.ResourceList { 209 var rl = v1.ResourceList{} 210 rl[v1.ResourceCPU] = *resource.NewMilliQuantity(int64(res.MilliCPU), resource.DecimalSI) 211 rl[v1.ResourceMemory] = *resource.NewQuantity(int64(res.Memory), resource.BinarySI) 212 for resourceName, f := range res.ScalarResources { 213 if resourceName == v1.ResourcePods { 214 rl[resourceName] = *resource.NewQuantity(int64(f), resource.DecimalSI) 215 continue 216 } 217 rl[resourceName] = *resource.NewMilliQuantity(int64(f), resource.DecimalSI) 218 } 219 return rl 220 }