volcano.sh/volcano@v1.9.0/pkg/scheduler/plugins/pdb/pdb.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 pdb
    18  
    19  import (
    20  	pdbPolicy "k8s.io/api/policy/v1"
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	"k8s.io/apimachinery/pkg/labels"
    23  	"k8s.io/client-go/informers"
    24  	policylisters "k8s.io/client-go/listers/policy/v1"
    25  	"k8s.io/klog/v2"
    26  
    27  	"volcano.sh/volcano/pkg/scheduler/api"
    28  	"volcano.sh/volcano/pkg/scheduler/framework"
    29  	"volcano.sh/volcano/pkg/scheduler/plugins/util"
    30  )
    31  
    32  // PluginName indicates name of volcano scheduler plugin
    33  const PluginName = "pdb"
    34  
    35  type pdbPlugin struct {
    36  	// Arguments given for pdb plugin
    37  	pluginArguments framework.Arguments
    38  	// Lister for PodDisruptionBudget
    39  	lister policylisters.PodDisruptionBudgetLister
    40  }
    41  
    42  // New function returns pdb plugin object
    43  func New(arguments framework.Arguments) framework.Plugin {
    44  	return &pdbPlugin{
    45  		pluginArguments: arguments,
    46  		lister:          nil,
    47  	}
    48  }
    49  
    50  // Name function returns pdb plugin name
    51  func (pp *pdbPlugin) Name() string {
    52  	return PluginName
    53  }
    54  
    55  func (pp *pdbPlugin) OnSessionOpen(ssn *framework.Session) {
    56  	klog.V(4).Infof("Enter pdb plugin ...")
    57  	defer klog.V(4).Infof("Leaving pdb plugin.")
    58  
    59  	// 0. Init the PDB lister
    60  	if pp.lister == nil {
    61  		pp.lister = getPDBLister(ssn.InformerFactory())
    62  	}
    63  
    64  	// 1. define the func to filter out tasks that violate PDB constraints
    65  	pdbFilterFn := func(tasks []*api.TaskInfo) []*api.TaskInfo {
    66  		var victims []*api.TaskInfo
    67  
    68  		// (a. get all PDBs
    69  		pdbs, err := getPodDisruptionBudgets(pp.lister)
    70  		if err != nil {
    71  			klog.Errorf("Failed to list pdbs condition: %v", err)
    72  			return victims
    73  		}
    74  
    75  		// (b. init the pdbsAllowed array
    76  		pdbsAllowed := make([]int32, len(pdbs))
    77  		for i, pdb := range pdbs {
    78  			pdbsAllowed[i] = pdb.Status.DisruptionsAllowed
    79  		}
    80  
    81  		// (c. range every task to check if it violates the PDB constraints.
    82  		// If task does not violate the PDB constraints, then add it to victims.
    83  		for _, task := range tasks {
    84  			pod := task.Pod
    85  			pdbForPodIsViolated := false
    86  
    87  			// A pod with no labels will not match any PDB. So, no need to check.
    88  			if len(pod.Labels) == 0 {
    89  				continue
    90  			}
    91  
    92  			for i, pdb := range pdbs {
    93  				if pdb.Namespace != pod.Namespace {
    94  					continue
    95  				}
    96  				selector, err := metav1.LabelSelectorAsSelector(pdb.Spec.Selector)
    97  				if err != nil {
    98  					continue
    99  				}
   100  				// A PDB with a nil or empty selector matches nothing.
   101  				if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) {
   102  					continue
   103  				}
   104  
   105  				// Existing in DisruptedPods means it has been processed in API server,
   106  				// we don't treat it as a violating case.
   107  				if _, exist := pdb.Status.DisruptedPods[pod.Name]; exist {
   108  					continue
   109  				}
   110  				// Only decrement the matched pdb when it's not in its <DisruptedPods>;
   111  				// otherwise we may over-decrement the budget number.
   112  				pdbsAllowed[i]--
   113  
   114  				if pdbsAllowed[i] < 0 {
   115  					pdbForPodIsViolated = true
   116  				}
   117  			}
   118  
   119  			if !pdbForPodIsViolated {
   120  				victims = append(victims, task)
   121  			} else {
   122  				klog.V(4).Infof("The pod <%s> of task <%s> violates the pdb constraint, so filter it from the victim list", task.Name, task.Pod.Name)
   123  			}
   124  		}
   125  		return victims
   126  	}
   127  
   128  	// 2. wrap pdbFilterFn to meet reclaimable and preemptable interface requirements
   129  	wrappedPdbFilterFn := func(preemptor *api.TaskInfo, preemptees []*api.TaskInfo) ([]*api.TaskInfo, int) {
   130  		return pdbFilterFn(preemptees), util.Permit
   131  	}
   132  
   133  	// 3. register VictimTasksFns, ReclaimableFn and PreemptableFn
   134  	victimsFns := []api.VictimTasksFn{pdbFilterFn}
   135  	ssn.AddVictimTasksFns(pp.Name(), victimsFns)
   136  	ssn.AddReclaimableFn(pp.Name(), wrappedPdbFilterFn)
   137  	ssn.AddPreemptableFn(pp.Name(), wrappedPdbFilterFn)
   138  }
   139  
   140  func (pp *pdbPlugin) OnSessionClose(ssn *framework.Session) {}
   141  
   142  // getPDBLister returns the lister of PodDisruptionBudget
   143  func getPDBLister(informerFactory informers.SharedInformerFactory) policylisters.PodDisruptionBudgetLister {
   144  	return informerFactory.Policy().V1().PodDisruptionBudgets().Lister()
   145  }
   146  
   147  // getPodDisruptionBudgets returns all pdbs
   148  func getPodDisruptionBudgets(pdbLister policylisters.PodDisruptionBudgetLister) ([]*pdbPolicy.PodDisruptionBudget, error) {
   149  	if pdbLister != nil {
   150  		return pdbLister.List(labels.Everything())
   151  	}
   152  	return nil, nil
   153  }