volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/predicates/predicates.go (about)

     1  /*
     2  Copyright 2018 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 predicates
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  	utilFeature "k8s.io/apiserver/pkg/util/feature"
    27  	"k8s.io/klog/v2"
    28  	"k8s.io/kubernetes/pkg/features"
    29  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    30  	k8sframework "k8s.io/kubernetes/pkg/scheduler/framework"
    31  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/feature"
    32  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/interpodaffinity"
    33  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeaffinity"
    34  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeports"
    35  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodeunschedulable"
    36  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/nodevolumelimits"
    37  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/podtopologyspread"
    38  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/tainttoleration"
    39  	"k8s.io/kubernetes/pkg/scheduler/framework/plugins/volumezone"
    40  
    41  	"volcano.sh/volcano/pkg/scheduler/api"
    42  	"volcano.sh/volcano/pkg/scheduler/framework"
    43  	"volcano.sh/volcano/pkg/scheduler/plugins/util/k8s"
    44  )
    45  
    46  const (
    47  	// PluginName indicates name of volcano scheduler plugin.
    48  	PluginName = "predicates"
    49  
    50  	// NodeAffinityEnable is the key for enabling Node Affinity Predicates in scheduler configmap
    51  	NodeAffinityEnable = "predicate.NodeAffinityEnable"
    52  
    53  	// NodePortsEnable is the key for enabling Node Port Predicates in scheduler configmap
    54  	NodePortsEnable = "predicate.NodePortsEnable"
    55  
    56  	// TaintTolerationEnable is the key for enabling Taint Toleration Predicates in scheduler configmap
    57  	TaintTolerationEnable = "predicate.TaintTolerationEnable"
    58  
    59  	// PodAffinityEnable is the key for enabling Pod Affinity Predicates in scheduler configmap
    60  	PodAffinityEnable = "predicate.PodAffinityEnable"
    61  
    62  	// NodeVolumeLimitsEnable is the key for enabling Node Volume Limits Predicates in scheduler configmap
    63  	NodeVolumeLimitsEnable = "predicate.NodeVolumeLimitsEnable"
    64  
    65  	// VolumeZoneEnable is the key for enabling Volume Zone Predicates in scheduler configmap
    66  	VolumeZoneEnable = "predicate.VolumeZoneEnable"
    67  
    68  	// PodTopologySpreadEnable is the key for enabling Pod Topology Spread Predicates in scheduler configmap
    69  	PodTopologySpreadEnable = "predicate.PodTopologySpreadEnable"
    70  
    71  	// CachePredicate control cache predicate feature
    72  	CachePredicate = "predicate.CacheEnable"
    73  
    74  	// ProportionalPredicate is the key for enabling Proportional Predicate in YAML
    75  	ProportionalPredicate = "predicate.ProportionalEnable"
    76  	// ProportionalResource is the key for additional resource key name
    77  	ProportionalResource = "predicate.resources"
    78  	// ProportionalResourcesPrefix is the key prefix for additional resource key name
    79  	ProportionalResourcesPrefix = ProportionalResource + "."
    80  )
    81  
    82  type predicatesPlugin struct {
    83  	// Arguments given for the plugin
    84  	pluginArguments framework.Arguments
    85  }
    86  
    87  // New return predicate plugin
    88  func New(arguments framework.Arguments) framework.Plugin {
    89  	return &predicatesPlugin{pluginArguments: arguments}
    90  }
    91  
    92  func (pp *predicatesPlugin) Name() string {
    93  	return PluginName
    94  }
    95  
    96  type baseResource struct {
    97  	CPU    float64
    98  	Memory float64
    99  }
   100  
   101  type predicateEnable struct {
   102  	nodeAffinityEnable      bool
   103  	nodePortEnable          bool
   104  	taintTolerationEnable   bool
   105  	podAffinityEnable       bool
   106  	nodeVolumeLimitsEnable  bool
   107  	volumeZoneEnable        bool
   108  	podTopologySpreadEnable bool
   109  	cacheEnable             bool
   110  	proportionalEnable      bool
   111  	proportional            map[v1.ResourceName]baseResource
   112  }
   113  
   114  func enablePredicate(args framework.Arguments) predicateEnable {
   115  	/*
   116  	   User Should give predicatesEnable in this format(predicate.GPUSharingEnable).
   117  	   Currently supported only GPUSharing predicate checks.
   118  
   119  	   actions: "reclaim, allocate, backfill, preempt"
   120  	   tiers:
   121  	   - plugins:
   122  	     - name: priority
   123  	     - name: gang
   124  	     - name: conformance
   125  	   - plugins:
   126  	     - name: drf
   127  	     - name: predicates
   128  	       arguments:
   129  	         predicate.NodeAffinityEnable: true
   130  	         predicate.NodePortsEnable: true
   131  	         predicate.TaintTolerationEnable: true
   132  	         predicate.PodAffinityEnable: true
   133  	         predicate.NodeVolumeLimitsEnable: true
   134  	         predicate.VolumeZoneEnable: true
   135  	         predicate.PodTopologySpreadEnable: true
   136  	         predicate.GPUSharingEnable: true
   137  	         predicate.GPUNumberEnable: true
   138  	         predicate.CacheEnable: true
   139  	         predicate.ProportionalEnable: true
   140  	         predicate.resources: nvidia.com/gpu
   141  	         predicate.resources.nvidia.com/gpu.cpu: 4
   142  	         predicate.resources.nvidia.com/gpu.memory: 8
   143  	     - name: proportion
   144  	     - name: nodeorder
   145  	*/
   146  
   147  	predicate := predicateEnable{
   148  		nodeAffinityEnable:      true,
   149  		nodePortEnable:          true,
   150  		taintTolerationEnable:   true,
   151  		podAffinityEnable:       true,
   152  		nodeVolumeLimitsEnable:  true,
   153  		volumeZoneEnable:        true,
   154  		podTopologySpreadEnable: true,
   155  		cacheEnable:             false,
   156  		proportionalEnable:      false,
   157  	}
   158  
   159  	// Checks whether predicate enable args is provided or not.
   160  	// If args were given by scheduler configmap, cover the values in predicateEnable struct.
   161  	args.GetBool(&predicate.nodeAffinityEnable, NodeAffinityEnable)
   162  	args.GetBool(&predicate.nodePortEnable, NodePortsEnable)
   163  	args.GetBool(&predicate.taintTolerationEnable, TaintTolerationEnable)
   164  	args.GetBool(&predicate.podAffinityEnable, PodAffinityEnable)
   165  	args.GetBool(&predicate.nodeVolumeLimitsEnable, NodeVolumeLimitsEnable)
   166  	args.GetBool(&predicate.volumeZoneEnable, VolumeZoneEnable)
   167  	args.GetBool(&predicate.podTopologySpreadEnable, PodTopologySpreadEnable)
   168  
   169  	args.GetBool(&predicate.cacheEnable, CachePredicate)
   170  	// Checks whether predicate.ProportionalEnable is provided or not, if given, modifies the value in predicateEnable struct.
   171  	args.GetBool(&predicate.proportionalEnable, ProportionalPredicate)
   172  	resourcesProportional := make(map[v1.ResourceName]baseResource)
   173  	resourcesStr, ok := args[ProportionalResource].(string)
   174  	if !ok {
   175  		resourcesStr = ""
   176  	}
   177  	resources := strings.Split(resourcesStr, ",")
   178  	for _, resource := range resources {
   179  		resource = strings.TrimSpace(resource)
   180  		if resource == "" {
   181  			continue
   182  		}
   183  		// proportional.resources.[ResourceName]
   184  		cpuResourceKey := ProportionalResourcesPrefix + resource + ".cpu"
   185  		cpuResourceRate := 1.0
   186  		args.GetFloat64(&cpuResourceRate, cpuResourceKey)
   187  		if cpuResourceRate < 0 {
   188  			cpuResourceRate = 1.0
   189  		}
   190  		memoryResourceKey := ProportionalResourcesPrefix + resource + ".memory"
   191  		memoryResourceRate := 1.0
   192  		args.GetFloat64(&memoryResourceRate, memoryResourceKey)
   193  		if memoryResourceRate < 0 {
   194  			memoryResourceRate = 1.0
   195  		}
   196  		r := baseResource{
   197  			CPU:    cpuResourceRate,
   198  			Memory: memoryResourceRate,
   199  		}
   200  		resourcesProportional[v1.ResourceName(resource)] = r
   201  	}
   202  	predicate.proportional = resourcesProportional
   203  
   204  	return predicate
   205  }
   206  
   207  func (pp *predicatesPlugin) OnSessionOpen(ssn *framework.Session) {
   208  	pl := ssn.PodLister
   209  	nodeMap := ssn.NodeMap
   210  
   211  	pCache := predicateCacheNew()
   212  	predicate := enablePredicate(pp.pluginArguments)
   213  
   214  	// Register event handlers to update task info in PodLister & nodeMap
   215  	ssn.AddEventHandler(&framework.EventHandler{
   216  		AllocateFunc: func(event *framework.Event) {
   217  			klog.V(4).Infoln("predicates, allocate", event.Task.NodeName)
   218  			pod := pl.UpdateTask(event.Task, event.Task.NodeName)
   219  			nodeName := event.Task.NodeName
   220  			node, found := nodeMap[nodeName]
   221  			if !found {
   222  				klog.Errorf("predicates, update pod %s/%s allocate to NOT EXIST node [%s]", pod.Namespace, pod.Name, nodeName)
   223  				return
   224  			}
   225  			nodeInfo, ok := ssn.Nodes[nodeName]
   226  			if !ok {
   227  				klog.Errorf("Failed to get node %s info from cache", nodeName)
   228  				return
   229  			}
   230  			//predicate gpu sharing
   231  			for _, val := range api.RegisteredDevices {
   232  				if devices, ok := nodeInfo.Others[val].(api.Devices); ok {
   233  					if !devices.HasDeviceRequest(pod) {
   234  						continue
   235  					}
   236  
   237  					err := devices.Allocate(ssn.KubeClient(), pod)
   238  					if err != nil {
   239  						klog.Errorf("AllocateToPod failed %s", err.Error())
   240  						return
   241  					}
   242  				} else {
   243  					klog.Warningf("Devices %s assertion conversion failed, skip", val)
   244  				}
   245  			}
   246  			node.AddPod(pod)
   247  			klog.V(4).Infof("predicates, update pod %s/%s allocate to node [%s]", pod.Namespace, pod.Name, nodeName)
   248  		},
   249  		DeallocateFunc: func(event *framework.Event) {
   250  			klog.V(4).Infoln("predicates, deallocate", event.Task.NodeName)
   251  			pod := pl.UpdateTask(event.Task, "")
   252  			nodeName := event.Task.NodeName
   253  			node, found := nodeMap[nodeName]
   254  			if !found {
   255  				klog.Errorf("predicates, update pod %s/%s allocate from NOT EXIST node [%s]", pod.Namespace, pod.Name, nodeName)
   256  				return
   257  			}
   258  
   259  			nodeInfo, ok := ssn.Nodes[nodeName]
   260  			if !ok {
   261  				klog.Errorf("Failed to get node %s info from cache", nodeName)
   262  				return
   263  			}
   264  
   265  			for _, val := range api.RegisteredDevices {
   266  				if devices, ok := nodeInfo.Others[val].(api.Devices); ok {
   267  					if !devices.HasDeviceRequest(pod) {
   268  						continue
   269  					}
   270  
   271  					// deallocate pod gpu id
   272  					err := devices.Release(ssn.KubeClient(), pod)
   273  					if err != nil {
   274  						klog.Errorf(err.Error())
   275  						return
   276  					}
   277  				} else {
   278  					klog.Warningf("Devices %s assertion conversion failed, skip", val)
   279  				}
   280  			}
   281  
   282  			err := node.RemovePod(klog.FromContext(context.TODO()), pod)
   283  			if err != nil {
   284  				klog.Errorf("predicates, remove pod %s/%s from node [%s] error: %v", pod.Namespace, pod.Name, nodeName, err)
   285  				return
   286  			}
   287  			klog.V(4).Infof("predicates, update pod %s/%s deallocate from node [%s]", pod.Namespace, pod.Name, nodeName)
   288  		},
   289  	})
   290  
   291  	features := feature.Features{
   292  		EnableVolumeCapacityPriority:                 utilFeature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
   293  		EnableMinDomainsInPodTopologySpread:          utilFeature.DefaultFeatureGate.Enabled(features.MinDomainsInPodTopologySpread),
   294  		EnableNodeInclusionPolicyInPodTopologySpread: utilFeature.DefaultFeatureGate.Enabled(features.NodeInclusionPolicyInPodTopologySpread),
   295  		EnableMatchLabelKeysInPodTopologySpread:      utilFeature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodTopologySpread),
   296  	}
   297  	// Initialize k8s plugins
   298  	// TODO: Add more predicates, k8s.io/kubernetes/pkg/scheduler/framework/plugins/legacy_registry.go
   299  	handle := k8s.NewFrameworkHandle(nodeMap, ssn.KubeClient(), ssn.InformerFactory())
   300  	// 1. NodeUnschedulable
   301  	plugin, _ := nodeunschedulable.New(context.TODO(), nil, handle)
   302  	nodeUnscheduleFilter := plugin.(*nodeunschedulable.NodeUnschedulable)
   303  	// 2. NodeAffinity
   304  	nodeAffinityArgs := config.NodeAffinityArgs{
   305  		AddedAffinity: &v1.NodeAffinity{},
   306  	}
   307  	plugin, _ = nodeaffinity.New(context.TODO(), &nodeAffinityArgs, handle)
   308  	nodeAffinityFilter := plugin.(*nodeaffinity.NodeAffinity)
   309  	// 3. NodePorts
   310  	plugin, _ = nodeports.New(context.TODO(), nil, handle)
   311  	nodePortFilter := plugin.(*nodeports.NodePorts)
   312  	// 4. TaintToleration
   313  	plugin, _ = tainttoleration.New(context.TODO(), nil, handle)
   314  	tolerationFilter := plugin.(*tainttoleration.TaintToleration)
   315  	// 5. InterPodAffinity
   316  	plArgs := &config.InterPodAffinityArgs{}
   317  	plugin, _ = interpodaffinity.New(context.TODO(), plArgs, handle)
   318  	podAffinityFilter := plugin.(*interpodaffinity.InterPodAffinity)
   319  	// 6. NodeVolumeLimits
   320  	plugin, _ = nodevolumelimits.NewCSI(context.TODO(), nil, handle, features)
   321  	nodeVolumeLimitsCSIFilter := plugin.(*nodevolumelimits.CSILimits)
   322  	// 7. VolumeZone
   323  	plugin, _ = volumezone.New(context.TODO(), nil, handle)
   324  	volumeZoneFilter := plugin.(*volumezone.VolumeZone)
   325  	// 8. PodTopologySpread
   326  	// Setting cluster level default constraints is not support for now.
   327  	ptsArgs := &config.PodTopologySpreadArgs{DefaultingType: config.SystemDefaulting}
   328  	plugin, _ = podtopologyspread.New(context.TODO(), ptsArgs, handle, features)
   329  	podTopologySpreadFilter := plugin.(*podtopologyspread.PodTopologySpread)
   330  
   331  	state := k8sframework.NewCycleState()
   332  	skipPlugins := make(map[api.TaskID]sets.Set[string])
   333  
   334  	ssn.AddPrePredicateFn(pp.Name(), func(task *api.TaskInfo) error {
   335  		// Check NodePorts
   336  		if predicate.nodePortEnable {
   337  			_, status := nodePortFilter.PreFilter(context.TODO(), state, task.Pod)
   338  			if err := handleSkipPrePredicatePlugin(status, task, skipPlugins, nodeports.Name); err != nil {
   339  				return err
   340  			}
   341  		}
   342  
   343  		// InterPodAffinity Predicate
   344  		// TODO: Update the node information to be processed by the filer based on the node list returned by the prefilter.
   345  		// In K8S V1.25, the return value result is added to the Prefile interface,
   346  		// indicating the list of nodes that meet filtering conditions.
   347  		// If the value of result is nil, all nodes meet the conditions.
   348  		// If the specified node information exists, only the node information in result meets the conditions.
   349  		// The value of Prefile in the current InterPodAffinity package always returns nil.
   350  		// The outer layer does not need to be processed temporarily.
   351  		// If the filtering logic is added to the Prefile node in the Volumebinding package in the future,
   352  		// the processing logic needs to be added to the return value result.
   353  		if predicate.podAffinityEnable {
   354  			_, status := podAffinityFilter.PreFilter(context.TODO(), state, task.Pod)
   355  			if err := handleSkipPrePredicatePlugin(status, task, skipPlugins, interpodaffinity.Name); err != nil {
   356  				return err
   357  			}
   358  		}
   359  
   360  		// Check PodTopologySpread
   361  		// TODO: Update the node information to be processed by the filer based on the node list returned by the prefilter.
   362  		// In K8S V1.25, the return value result is added to the Prefile interface,
   363  		// indicating the list of nodes that meet filtering conditions.
   364  		// If the value of result is nil, all nodes meet the conditions.
   365  		// If the specified node information exists, only the node information in result meets the conditions.
   366  		// The value of Prefile in the current PodTopologySpread package always returns nil.
   367  		// The outer layer does not need to be processed temporarily.
   368  		// If the filtering logic is added to the Prefile node in the Volumebinding package in the future,
   369  		// the processing logic needs to be added to the return value result.
   370  		if predicate.podTopologySpreadEnable {
   371  			_, status := podTopologySpreadFilter.PreFilter(context.TODO(), state, task.Pod)
   372  			if err := handleSkipPrePredicatePlugin(status, task, skipPlugins, podTopologySpreadFilter.Name()); err != nil {
   373  				return err
   374  			}
   375  		}
   376  		return nil
   377  	})
   378  
   379  	ssn.AddPredicateFn(pp.Name(), func(task *api.TaskInfo, node *api.NodeInfo) ([]*api.Status, error) {
   380  		predicateStatus := make([]*api.Status, 0)
   381  		nodeInfo, found := nodeMap[node.Name]
   382  		if !found {
   383  			return predicateStatus, fmt.Errorf("failed to predicates, node info for %s not found", node.Name)
   384  		}
   385  
   386  		if node.Allocatable.MaxTaskNum <= len(nodeInfo.Pods) {
   387  			klog.V(4).Infof("NodePodNumber predicates Task <%s/%s> on Node <%s> failed, allocatable <%d>, existed <%d>",
   388  				task.Namespace, task.Name, node.Name, node.Allocatable.MaxTaskNum, len(nodeInfo.Pods))
   389  			podsNumStatus := &api.Status{
   390  				Code:   api.Unschedulable,
   391  				Reason: api.NodePodNumberExceeded,
   392  			}
   393  			predicateStatus = append(predicateStatus, podsNumStatus)
   394  		}
   395  
   396  		predicateByStablefilter := func(pod *v1.Pod, nodeInfo *k8sframework.NodeInfo) ([]*api.Status, bool, error) {
   397  			// CheckNodeUnschedulable
   398  			predicateStatus := make([]*api.Status, 0)
   399  			status := nodeUnscheduleFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   400  			nodeUnscheduleStatus := framework.ConvertPredicateStatus(status)
   401  			if nodeUnscheduleStatus.Code != api.Success {
   402  				predicateStatus = append(predicateStatus, nodeUnscheduleStatus)
   403  				return predicateStatus, false, fmt.Errorf("plugin %s predicates failed %s", nodeUnscheduleFilter.Name(), status.Message())
   404  			}
   405  
   406  			// Check NodeAffinity
   407  			if predicate.nodeAffinityEnable {
   408  				status := nodeAffinityFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   409  				nodeAffinityStatus := framework.ConvertPredicateStatus(status)
   410  				if nodeAffinityStatus.Code != api.Success {
   411  					predicateStatus = append(predicateStatus, nodeAffinityStatus)
   412  					return predicateStatus, false, fmt.Errorf("plugin %s predicates failed %s", nodeAffinityFilter.Name(), status.Message())
   413  				}
   414  			}
   415  
   416  			// PodToleratesNodeTaints: TaintToleration
   417  			if predicate.taintTolerationEnable {
   418  				status := tolerationFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   419  				tolerationStatus := framework.ConvertPredicateStatus(status)
   420  				if tolerationStatus.Code != api.Success {
   421  					predicateStatus = append(predicateStatus, tolerationStatus)
   422  					return predicateStatus, false, fmt.Errorf("plugin %s predicates failed %s", tolerationFilter.Name(), status.Message())
   423  				}
   424  			}
   425  
   426  			return predicateStatus, true, nil
   427  		}
   428  
   429  		// Check PredicateWithCache
   430  		var err error
   431  		var fit bool
   432  		predicateCacheStatus := make([]*api.Status, 0)
   433  		if predicate.cacheEnable {
   434  			fit, err = pCache.PredicateWithCache(node.Name, task.Pod)
   435  			if err != nil {
   436  				predicateCacheStatus, fit, err = predicateByStablefilter(task.Pod, nodeInfo)
   437  				pCache.UpdateCache(node.Name, task.Pod, fit)
   438  			} else {
   439  				if !fit {
   440  					err = fmt.Errorf("plugin equivalence cache predicates failed")
   441  				}
   442  			}
   443  		} else {
   444  			predicateCacheStatus, fit, err = predicateByStablefilter(task.Pod, nodeInfo)
   445  		}
   446  
   447  		predicateStatus = append(predicateStatus, predicateCacheStatus...)
   448  		if !fit {
   449  			return predicateStatus, err
   450  		}
   451  
   452  		// Check NodePort
   453  		if predicate.nodePortEnable {
   454  			isSkipNodePorts := handleSkipPredicatePlugin(task, skipPlugins, nodePortFilter.Name(), node)
   455  			if !isSkipNodePorts {
   456  				status := nodePortFilter.Filter(context.TODO(), state, nil, nodeInfo)
   457  				nodePortStatus := framework.ConvertPredicateStatus(status)
   458  				if nodePortStatus.Code != api.Success {
   459  					predicateStatus = append(predicateStatus, nodePortStatus)
   460  					return predicateStatus, fmt.Errorf("plugin %s predicates failed %s", nodePortFilter.Name(), status.Message())
   461  				}
   462  			}
   463  		}
   464  
   465  		// Check PodAffinity
   466  		if predicate.podAffinityEnable {
   467  			isSkipInterPodAffinity := handleSkipPredicatePlugin(task, skipPlugins, podAffinityFilter.Name(), node)
   468  			if !isSkipInterPodAffinity {
   469  				status := podAffinityFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   470  				podAffinityStatus := framework.ConvertPredicateStatus(status)
   471  				if podAffinityStatus.Code != api.Success {
   472  					predicateStatus = append(predicateStatus, podAffinityStatus)
   473  					return predicateStatus, fmt.Errorf("plugin %s predicates failed %s", podAffinityFilter.Name(), status.Message())
   474  				}
   475  			}
   476  		}
   477  
   478  		// Check NodeVolumeLimits
   479  		if predicate.nodeVolumeLimitsEnable {
   480  			status := nodeVolumeLimitsCSIFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   481  			nodeVolumeStatus := framework.ConvertPredicateStatus(status)
   482  			predicateStatus = append(predicateStatus, nodeVolumeStatus)
   483  		}
   484  
   485  		// Check VolumeZone
   486  		if predicate.volumeZoneEnable {
   487  			status := volumeZoneFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   488  			volumeZoneStatus := framework.ConvertPredicateStatus(status)
   489  			if volumeZoneStatus.Code != api.Success {
   490  				predicateStatus = append(predicateStatus, volumeZoneStatus)
   491  				return predicateStatus, fmt.Errorf("plugin %s predicates failed %s", volumeZoneFilter.Name(), status.Message())
   492  			}
   493  		}
   494  
   495  		// Check PodTopologySpread
   496  		if predicate.podTopologySpreadEnable {
   497  			isSkipPodTopologySpreadFilter := handleSkipPredicatePlugin(task, skipPlugins, podTopologySpreadFilter.Name(), node)
   498  			if !isSkipPodTopologySpreadFilter {
   499  				status := podTopologySpreadFilter.Filter(context.TODO(), state, task.Pod, nodeInfo)
   500  				podTopologyStatus := framework.ConvertPredicateStatus(status)
   501  				if podTopologyStatus.Code != api.Success {
   502  					predicateStatus = append(predicateStatus, podTopologyStatus)
   503  					return predicateStatus, fmt.Errorf("plugin %s predicates failed %s", podTopologySpreadFilter.Name(), status.Message())
   504  				}
   505  			}
   506  		}
   507  
   508  		if predicate.proportionalEnable {
   509  			// Check ProportionalPredicate
   510  			proportionalStatus, err := checkNodeResourceIsProportional(task, node, predicate.proportional)
   511  			if proportionalStatus.Code != api.Success {
   512  				predicateStatus = append(predicateStatus, proportionalStatus)
   513  				return predicateStatus, err
   514  			}
   515  			klog.V(4).Infof("checkNodeResourceIsProportional predicates Task <%s/%s> on Node <%s>: fit %v",
   516  				task.Namespace, task.Name, node.Name, fit)
   517  		}
   518  		return predicateStatus, nil
   519  	})
   520  }
   521  
   522  func handleSkipPredicatePlugin(task *api.TaskInfo, skipPlugins map[api.TaskID]sets.Set[string], pluginName string, node *api.NodeInfo) bool {
   523  	isSkipPluginFilter := false
   524  	taskKey := api.PodKey(task.Pod)
   525  	if plugins, ok := skipPlugins[taskKey]; ok {
   526  		if plugins.Has(pluginName) {
   527  			isSkipPluginFilter = true
   528  			klog.V(5).Infof("pod(%s/%s) affinity require information is nil, plugin %s is skip for node %s",
   529  				task.Namespace, task.Name, pluginName, node.Name)
   530  		}
   531  	}
   532  	return isSkipPluginFilter
   533  }
   534  
   535  func handleSkipPrePredicatePlugin(status *k8sframework.Status, task *api.TaskInfo, skipPlugins map[api.TaskID]sets.Set[string], pluginName string) error {
   536  	if status.IsSkip() {
   537  		taskKey := api.PodKey(task.Pod)
   538  		if _, ok := skipPlugins[taskKey]; !ok {
   539  			plugins := sets.New[string]()
   540  			skipPlugins[taskKey] = plugins
   541  		}
   542  		skipPlugins[taskKey].Insert(pluginName)
   543  		klog.V(5).Infof("pod(%s/%s) affinity require information is nil, plugin %s is skipped",
   544  			task.Namespace, task.Name, pluginName)
   545  	} else if !status.IsSuccess() {
   546  		return fmt.Errorf("plugin %s pre-predicates failed %s", pluginName, status.Message())
   547  	}
   548  	return nil
   549  }
   550  
   551  func (pp *predicatesPlugin) OnSessionClose(ssn *framework.Session) {}