sigs.k8s.io/kueue@v0.6.2/pkg/util/limitrange/limitrange.go (about)

     1  /*
     2  Copyright 2023 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 limitrange
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  	"k8s.io/utils/field"
    25  
    26  	"sigs.k8s.io/kueue/pkg/util/resource"
    27  )
    28  
    29  type Summary map[corev1.LimitType]corev1.LimitRangeItem
    30  
    31  // Summarize summarizes the provides ranges by:
    32  // 1. keeping the lowest values for Max and MaxLimitReqestRatio limits
    33  // 2. keeping the highest values for Min limits
    34  // 3. keeping the first encountered values for Default and DefaultRequests
    35  func Summarize(ranges ...corev1.LimitRange) Summary {
    36  	ret := make(Summary, 3)
    37  	for i := range ranges {
    38  		for _, item := range ranges[i].Spec.Limits {
    39  			ret[item.Type] = addToSummary(ret[item.Type], item)
    40  		}
    41  	}
    42  	return ret
    43  }
    44  
    45  func addToSummary(summary, item corev1.LimitRangeItem) corev1.LimitRangeItem {
    46  	summary.Max = resource.MergeResourceListKeepMin(summary.Max, item.Max)
    47  	summary.Min = resource.MergeResourceListKeepMax(summary.Min, item.Min)
    48  
    49  	summary.Default = resource.MergeResourceListKeepFirst(summary.Default, item.Default)
    50  	summary.DefaultRequest = resource.MergeResourceListKeepFirst(summary.DefaultRequest, item.DefaultRequest)
    51  
    52  	summary.MaxLimitRequestRatio = resource.MergeResourceListKeepMin(summary.MaxLimitRequestRatio, item.MaxLimitRequestRatio)
    53  	return summary
    54  }
    55  
    56  func violateMaxMessage(path *field.Path, keys ...string) string {
    57  	return fmt.Sprintf("the requests of %s[%s] exceeds the limits", path.String(), strings.Join(keys, ", "))
    58  }
    59  func violateMinMessage(path *field.Path, keys ...string) string {
    60  	return fmt.Sprintf("the requests of %s[%s] are less than the limits", path.String(), strings.Join(keys, ", "))
    61  }
    62  
    63  func (s Summary) validatePodSpecContainers(containers []corev1.Container, path *field.Path) []string {
    64  	containerRange, found := s[corev1.LimitTypeContainer]
    65  	if !found {
    66  		return nil
    67  	}
    68  	reasons := []string{}
    69  	for i := range containers {
    70  		res := &containers[i].Resources
    71  		cMin := resource.MergeResourceListKeepMin(res.Requests, res.Limits)
    72  		cMax := resource.MergeResourceListKeepMax(res.Requests, res.Limits)
    73  		if list := resource.GetGreaterKeys(cMax, containerRange.Max); len(list) > 0 {
    74  			reasons = append(reasons, violateMaxMessage(path.Index(i), list...))
    75  		}
    76  		if list := resource.GetGreaterKeys(containerRange.Min, cMin); len(list) > 0 {
    77  			reasons = append(reasons, violateMinMessage(path.Index(i), list...))
    78  		}
    79  	}
    80  	return reasons
    81  }
    82  
    83  // TotalRequests computes the total resource requests of a pod.
    84  // total = sum(max(sum(.containers[].requests), initContainers[].requests), overhead)
    85  func TotalRequests(ps *corev1.PodSpec) corev1.ResourceList {
    86  	total := corev1.ResourceList{}
    87  
    88  	// add the resource from the main containers
    89  	for i := range ps.Containers {
    90  		total = resource.MergeResourceListKeepSum(total, ps.Containers[i].Resources.Requests)
    91  	}
    92  
    93  	// take into account the maximum of any init containers
    94  	for i := range ps.InitContainers {
    95  		total = resource.MergeResourceListKeepMax(total, ps.InitContainers[i].Resources.Requests)
    96  	}
    97  
    98  	// add the overhead
    99  	total = resource.MergeResourceListKeepSum(total, ps.Overhead)
   100  	return total
   101  }
   102  
   103  // ValidatePodSpec verifies if the provided podSpec (ps) first into the boundaries of the summary (s).
   104  func (s Summary) ValidatePodSpec(ps *corev1.PodSpec, path *field.Path) []string {
   105  	reasons := []string{}
   106  	reasons = append(reasons, s.validatePodSpecContainers(ps.InitContainers, path.Child("initContainers"))...)
   107  	reasons = append(reasons, s.validatePodSpecContainers(ps.Containers, path.Child("containers"))...)
   108  	if containerRange, found := s[corev1.LimitTypePod]; found {
   109  		total := TotalRequests(ps)
   110  		if list := resource.GetGreaterKeys(total, containerRange.Max); len(list) > 0 {
   111  			reasons = append(reasons, violateMaxMessage(path, list...))
   112  		}
   113  		if list := resource.GetGreaterKeys(containerRange.Min, total); len(list) > 0 {
   114  			reasons = append(reasons, violateMinMessage(path, list...))
   115  		}
   116  	}
   117  	return reasons
   118  }