volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/nodegroup/nodegroup.go (about) 1 /* 2 Copyright 2023 The Volcano 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 nodegroup 18 19 import ( 20 "errors" 21 "fmt" 22 23 "k8s.io/apimachinery/pkg/util/sets" 24 25 "k8s.io/klog/v2" 26 27 batch "volcano.sh/apis/pkg/apis/batch/v1alpha1" 28 sch "volcano.sh/apis/pkg/apis/scheduling/v1beta1" 29 "volcano.sh/volcano/pkg/scheduler/api" 30 "volcano.sh/volcano/pkg/scheduler/framework" 31 ) 32 33 const ( 34 // PluginName indicates name of volcano scheduler plugin. 35 PluginName = "nodegroup" 36 NodeGroupNameKey = "volcano.sh/nodegroup-name" 37 38 BaseScore = 100 39 ) 40 41 type nodeGroupPlugin struct { 42 // Arguments given for the plugin 43 pluginArguments framework.Arguments 44 } 45 46 // New function returns prioritize plugin object. 47 func New(arguments framework.Arguments) framework.Plugin { 48 return &nodeGroupPlugin{pluginArguments: arguments} 49 } 50 51 func (pp *nodeGroupPlugin) Name() string { 52 return PluginName 53 } 54 55 type queueGroupAffinity struct { 56 queueGroupAntiAffinityRequired map[string]sets.Set[string] 57 queueGroupAntiAffinityPreferred map[string]sets.Set[string] 58 queueGroupAffinityRequired map[string]sets.Set[string] 59 queueGroupAffinityPreferred map[string]sets.Set[string] 60 } 61 62 func NewQueueGroupAffinity() queueGroupAffinity { 63 return queueGroupAffinity{ 64 queueGroupAntiAffinityRequired: make(map[string]sets.Set[string], 0), 65 queueGroupAntiAffinityPreferred: make(map[string]sets.Set[string], 0), 66 queueGroupAffinityRequired: make(map[string]sets.Set[string], 0), 67 queueGroupAffinityPreferred: make(map[string]sets.Set[string], 0), 68 } 69 } 70 71 func (q queueGroupAffinity) predicate(queue, group string) error { 72 if len(queue) == 0 { 73 return nil 74 } 75 flag := false 76 if q.queueGroupAffinityRequired != nil { 77 if groups, ok := q.queueGroupAffinityRequired[queue]; ok { 78 if groups.Has(group) { 79 flag = true 80 } 81 } 82 } 83 if q.queueGroupAffinityPreferred != nil { 84 if groups, ok := q.queueGroupAffinityPreferred[queue]; ok { 85 if groups.Has(group) { 86 flag = true 87 } 88 } 89 } 90 // AntiAffinity: hard constraints should be checked first 91 // to make sure soft constraints satisfy 92 // and antiAffinity's priority is higher than affinity 93 if q.queueGroupAntiAffinityRequired != nil { 94 if groups, ok := q.queueGroupAntiAffinityRequired[queue]; ok { 95 if groups.Has(group) { 96 flag = false 97 } 98 } 99 } 100 if q.queueGroupAntiAffinityPreferred != nil { 101 if groups, ok := q.queueGroupAntiAffinityPreferred[queue]; ok { 102 if groups.Has(group) { 103 flag = true 104 } 105 } 106 } 107 if !flag { 108 return errors.New("not satisfy") 109 } 110 return nil 111 } 112 113 func (q queueGroupAffinity) score(queue string, group string) float64 { 114 nodeScore := 0.0 115 if len(queue) == 0 { 116 return nodeScore 117 } 118 // Affinity: hard constraints should be checked first 119 // to make sure soft constraints can cover score. 120 // And same to predict, antiAffinity's priority is higher than affinity 121 if q.queueGroupAffinityRequired != nil { 122 if groups, ok := q.queueGroupAffinityRequired[queue]; ok { 123 if groups.Has(group) { 124 nodeScore += BaseScore 125 } 126 } 127 } 128 if q.queueGroupAffinityPreferred != nil { 129 if groups, ok := q.queueGroupAffinityPreferred[queue]; ok { 130 if groups.Has(group) { 131 nodeScore += 0.5 * BaseScore 132 } 133 } 134 } 135 if q.queueGroupAntiAffinityPreferred != nil { 136 if groups, ok := q.queueGroupAntiAffinityPreferred[queue]; ok { 137 if groups.Has(group) { 138 nodeScore = -1 139 } 140 } 141 } 142 143 return nodeScore 144 } 145 146 // 147 // User should specify arguments in the config in this format: 148 // 149 // actions: "reclaim, allocate, backfill, preempt" 150 // tiers: 151 // - plugins: 152 // - name: priority 153 // - name: gang 154 // - name: conformance 155 // - plugins: 156 // - name: drf 157 // - name: predicates 158 // - name: proportion 159 // - name: nodegroup 160 161 func calculateArguments(ssn *framework.Session, args framework.Arguments) queueGroupAffinity { 162 queueGroupAffinity := NewQueueGroupAffinity() 163 for _, queue := range ssn.Queues { 164 affinity := queue.Queue.Spec.Affinity 165 if affinity == nil { 166 continue 167 } 168 nodeGroupAffinity := affinity.NodeGroupAffinity 169 if nodeGroupAffinity != nil { 170 preferreds := sets.Set[string]{} 171 preferreds = preferreds.Insert(nodeGroupAffinity.PreferredDuringSchedulingIgnoredDuringExecution...) 172 if len(preferreds) > 0 { 173 queueGroupAffinity.queueGroupAffinityPreferred[queue.Name] = preferreds 174 } 175 requireds := sets.Set[string]{} 176 requireds = requireds.Insert(nodeGroupAffinity.RequiredDuringSchedulingIgnoredDuringExecution...) 177 if len(requireds) > 0 { 178 queueGroupAffinity.queueGroupAffinityRequired[queue.Name] = requireds 179 } 180 } 181 nodeGroupAntiAffinity := affinity.NodeGroupAntiAffinity 182 if nodeGroupAntiAffinity != nil { 183 preferreds := sets.Set[string]{} 184 preferreds = preferreds.Insert(nodeGroupAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution...) 185 if len(preferreds) > 0 { 186 queueGroupAffinity.queueGroupAntiAffinityPreferred[queue.Name] = preferreds 187 } 188 requireds := sets.Set[string]{} 189 requireds = requireds.Insert(nodeGroupAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution...) 190 if len(requireds) > 0 { 191 queueGroupAffinity.queueGroupAntiAffinityRequired[queue.Name] = requireds 192 } 193 } 194 } 195 return queueGroupAffinity 196 } 197 198 // There are 3 ways to assign pod to queue for now: 199 // scheduling.volcano.sh/queue-name support only annotation 200 // volcano.sh/queue-name support both labels & annotation 201 // the key should be unified, maybe volcano.sh/queue-name is better 202 func GetPodQueue(task *api.TaskInfo) string { 203 if _, ok := task.Pod.Labels[batch.QueueNameKey]; ok { 204 return task.Pod.Labels[batch.QueueNameKey] 205 } 206 if _, ok := task.Pod.Annotations[batch.QueueNameKey]; ok { 207 return task.Pod.Annotations[batch.QueueNameKey] 208 } 209 if _, ok := task.Pod.Annotations[sch.QueueNameAnnotationKey]; ok { 210 return task.Pod.Annotations[sch.QueueNameAnnotationKey] 211 } 212 return "" 213 } 214 215 func (np *nodeGroupPlugin) OnSessionOpen(ssn *framework.Session) { 216 queueGroupAffinity := calculateArguments(ssn, np.pluginArguments) 217 klog.V(4).Infof("queueGroupAffinity queueGroupAntiAffinityRequired <%v> queueGroupAntiAffinityPreferred <%v> queueGroupAffinityRequired <%v> queueGroupAffinityPreferred <%v> groupLabelName <%v>", 218 queueGroupAffinity.queueGroupAntiAffinityRequired, queueGroupAffinity.queueGroupAntiAffinityPreferred, 219 queueGroupAffinity.queueGroupAffinityRequired, queueGroupAffinity.queueGroupAffinityPreferred, NodeGroupNameKey) 220 nodeOrderFn := func(task *api.TaskInfo, node *api.NodeInfo) (float64, error) { 221 group := node.Node.Labels[NodeGroupNameKey] 222 queue := GetPodQueue(task) 223 score := queueGroupAffinity.score(queue, group) 224 klog.V(4).Infof("task <%s>/<%s> queue %s on node %s of nodegroup %s, score %v", task.Namespace, task.Name, queue, node.Name, group, score) 225 return score, nil 226 } 227 ssn.AddNodeOrderFn(np.Name(), nodeOrderFn) 228 229 predicateFn := func(task *api.TaskInfo, node *api.NodeInfo) ([]*api.Status, error) { 230 predicateStatus := make([]*api.Status, 0) 231 232 group := node.Node.Labels[NodeGroupNameKey] 233 queue := GetPodQueue(task) 234 if err := queueGroupAffinity.predicate(queue, group); err != nil { 235 nodeStatus := &api.Status{ 236 Code: api.UnschedulableAndUnresolvable, 237 Reason: "node not satisfy", 238 } 239 predicateStatus = append(predicateStatus, nodeStatus) 240 return predicateStatus, fmt.Errorf("<%s> predicates Task <%s/%s> on Node <%s> of nodegroup <%v> failed <%v>", np.Name(), task.Namespace, task.Name, node.Name, group, err) 241 } 242 klog.V(4).Infof("task <%s>/<%s> queue %s on node %s of nodegroup %v", task.Namespace, task.Name, queue, node.Name, group) 243 nodeStatus := &api.Status{ 244 Code: api.Success, 245 Reason: "node satisfy task", 246 } 247 predicateStatus = append(predicateStatus, nodeStatus) 248 return predicateStatus, nil 249 } 250 251 ssn.AddPredicateFn(np.Name(), predicateFn) 252 } 253 254 func (np *nodeGroupPlugin) OnSessionClose(ssn *framework.Session) { 255 }