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  }