...

Source file src/k8s.io/kubectl/pkg/util/resource/resource.go

Documentation: k8s.io/kubectl/pkg/util/resource

     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 resource
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"strconv"
    23  	"strings"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/resource"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  )
    29  
    30  // PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
    31  // containers of the pod. If pod overhead is non-nil, the pod overhead is added to the
    32  // total container resource requests and to the total container limits which have a
    33  // non-zero quantity.
    34  func PodRequestsAndLimits(pod *corev1.Pod) (reqs, limits corev1.ResourceList) {
    35  	return podRequests(pod), podLimits(pod)
    36  }
    37  
    38  // podRequests is a simplified form of PodRequests from k8s.io/kubernetes/pkg/api/v1/resource that doesn't check
    39  // feature gate enablement and avoids adding a dependency on k8s.io/kubernetes/pkg/apis/core/v1 for kubectl.
    40  func podRequests(pod *corev1.Pod) corev1.ResourceList {
    41  	// attempt to reuse the maps if passed, or allocate otherwise
    42  	reqs := corev1.ResourceList{}
    43  
    44  	containerStatuses := map[string]*corev1.ContainerStatus{}
    45  	for i := range pod.Status.ContainerStatuses {
    46  		containerStatuses[pod.Status.ContainerStatuses[i].Name] = &pod.Status.ContainerStatuses[i]
    47  	}
    48  
    49  	for _, container := range pod.Spec.Containers {
    50  		containerReqs := container.Resources.Requests
    51  		cs, found := containerStatuses[container.Name]
    52  		if found {
    53  			if pod.Status.Resize == corev1.PodResizeStatusInfeasible {
    54  				containerReqs = cs.AllocatedResources.DeepCopy()
    55  			} else {
    56  				containerReqs = max(container.Resources.Requests, cs.AllocatedResources)
    57  			}
    58  		}
    59  		addResourceList(reqs, containerReqs)
    60  	}
    61  
    62  	restartableInitContainerReqs := corev1.ResourceList{}
    63  	initContainerReqs := corev1.ResourceList{}
    64  
    65  	for _, container := range pod.Spec.InitContainers {
    66  		containerReqs := container.Resources.Requests
    67  
    68  		if container.RestartPolicy != nil && *container.RestartPolicy == corev1.ContainerRestartPolicyAlways {
    69  			// and add them to the resulting cumulative container requests
    70  			addResourceList(reqs, containerReqs)
    71  
    72  			// track our cumulative restartable init container resources
    73  			addResourceList(restartableInitContainerReqs, containerReqs)
    74  			containerReqs = restartableInitContainerReqs
    75  		} else {
    76  			tmp := corev1.ResourceList{}
    77  			addResourceList(tmp, containerReqs)
    78  			addResourceList(tmp, restartableInitContainerReqs)
    79  			containerReqs = tmp
    80  		}
    81  		maxResourceList(initContainerReqs, containerReqs)
    82  	}
    83  
    84  	maxResourceList(reqs, initContainerReqs)
    85  
    86  	// Add overhead for running a pod to the sum of requests if requested:
    87  	if pod.Spec.Overhead != nil {
    88  		addResourceList(reqs, pod.Spec.Overhead)
    89  	}
    90  
    91  	return reqs
    92  }
    93  
    94  // podLimits is a simplified form of PodLimits from k8s.io/kubernetes/pkg/api/v1/resource that doesn't check
    95  // feature gate enablement and avoids adding a dependency on k8s.io/kubernetes/pkg/apis/core/v1 for kubectl.
    96  func podLimits(pod *corev1.Pod) corev1.ResourceList {
    97  	limits := corev1.ResourceList{}
    98  
    99  	for _, container := range pod.Spec.Containers {
   100  		addResourceList(limits, container.Resources.Limits)
   101  	}
   102  
   103  	restartableInitContainerLimits := corev1.ResourceList{}
   104  	initContainerLimits := corev1.ResourceList{}
   105  
   106  	for _, container := range pod.Spec.InitContainers {
   107  		containerLimits := container.Resources.Limits
   108  		// Is the init container marked as a restartable init container?
   109  		if container.RestartPolicy != nil && *container.RestartPolicy == corev1.ContainerRestartPolicyAlways {
   110  			addResourceList(limits, containerLimits)
   111  
   112  			// track our cumulative restartable init container resources
   113  			addResourceList(restartableInitContainerLimits, containerLimits)
   114  			containerLimits = restartableInitContainerLimits
   115  		} else {
   116  			tmp := corev1.ResourceList{}
   117  			addResourceList(tmp, containerLimits)
   118  			addResourceList(tmp, restartableInitContainerLimits)
   119  			containerLimits = tmp
   120  		}
   121  
   122  		maxResourceList(initContainerLimits, containerLimits)
   123  	}
   124  
   125  	maxResourceList(limits, initContainerLimits)
   126  
   127  	// Add overhead to non-zero limits if requested:
   128  	if pod.Spec.Overhead != nil {
   129  		for name, quantity := range pod.Spec.Overhead {
   130  			if value, ok := limits[name]; ok && !value.IsZero() {
   131  				value.Add(quantity)
   132  				limits[name] = value
   133  			}
   134  		}
   135  	}
   136  
   137  	return limits
   138  }
   139  
   140  // max returns the result of max(a, b) for each named resource and is only used if we can't
   141  // accumulate into an existing resource list
   142  func max(a corev1.ResourceList, b corev1.ResourceList) corev1.ResourceList {
   143  	result := corev1.ResourceList{}
   144  	for key, value := range a {
   145  		if other, found := b[key]; found {
   146  			if value.Cmp(other) <= 0 {
   147  				result[key] = other.DeepCopy()
   148  				continue
   149  			}
   150  		}
   151  		result[key] = value.DeepCopy()
   152  	}
   153  	for key, value := range b {
   154  		if _, found := result[key]; !found {
   155  			result[key] = value.DeepCopy()
   156  		}
   157  	}
   158  	return result
   159  }
   160  
   161  // addResourceList adds the resources in newList to list
   162  func addResourceList(list, new corev1.ResourceList) {
   163  	for name, quantity := range new {
   164  		if value, ok := list[name]; !ok {
   165  			list[name] = quantity.DeepCopy()
   166  		} else {
   167  			value.Add(quantity)
   168  			list[name] = value
   169  		}
   170  	}
   171  }
   172  
   173  // maxResourceList sets list to the greater of list/newList for every resource
   174  // either list
   175  func maxResourceList(list, new corev1.ResourceList) {
   176  	for name, quantity := range new {
   177  		if value, ok := list[name]; !ok {
   178  			list[name] = quantity.DeepCopy()
   179  			continue
   180  		} else {
   181  			if quantity.Cmp(value) > 0 {
   182  				list[name] = quantity.DeepCopy()
   183  			}
   184  		}
   185  	}
   186  }
   187  
   188  // ExtractContainerResourceValue extracts the value of a resource
   189  // in an already known container
   190  func ExtractContainerResourceValue(fs *corev1.ResourceFieldSelector, container *corev1.Container) (string, error) {
   191  	divisor := resource.Quantity{}
   192  	if divisor.Cmp(fs.Divisor) == 0 {
   193  		divisor = resource.MustParse("1")
   194  	} else {
   195  		divisor = fs.Divisor
   196  	}
   197  
   198  	switch fs.Resource {
   199  	case "limits.cpu":
   200  		return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
   201  	case "limits.memory":
   202  		return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
   203  	case "limits.ephemeral-storage":
   204  		return convertResourceEphemeralStorageToString(container.Resources.Limits.StorageEphemeral(), divisor)
   205  	case "requests.cpu":
   206  		return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
   207  	case "requests.memory":
   208  		return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
   209  	case "requests.ephemeral-storage":
   210  		return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor)
   211  	}
   212  	// handle extended standard resources with dynamic names
   213  	// example: requests.hugepages-<pageSize> or limits.hugepages-<pageSize>
   214  	if strings.HasPrefix(fs.Resource, "requests.") {
   215  		resourceName := corev1.ResourceName(strings.TrimPrefix(fs.Resource, "requests."))
   216  		if IsHugePageResourceName(resourceName) {
   217  			return convertResourceHugePagesToString(container.Resources.Requests.Name(resourceName, resource.BinarySI), divisor)
   218  		}
   219  	}
   220  	if strings.HasPrefix(fs.Resource, "limits.") {
   221  		resourceName := corev1.ResourceName(strings.TrimPrefix(fs.Resource, "limits."))
   222  		if IsHugePageResourceName(resourceName) {
   223  			return convertResourceHugePagesToString(container.Resources.Limits.Name(resourceName, resource.BinarySI), divisor)
   224  		}
   225  	}
   226  	return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
   227  }
   228  
   229  // convertResourceCPUToString converts cpu value to the format of divisor and returns
   230  // ceiling of the value.
   231  func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
   232  	c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
   233  	return strconv.FormatInt(c, 10), nil
   234  }
   235  
   236  // convertResourceMemoryToString converts memory value to the format of divisor and returns
   237  // ceiling of the value.
   238  func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
   239  	m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
   240  	return strconv.FormatInt(m, 10), nil
   241  }
   242  
   243  // convertResourceHugePagesToString converts hugepages value to the format of divisor and returns
   244  // ceiling of the value.
   245  func convertResourceHugePagesToString(hugePages *resource.Quantity, divisor resource.Quantity) (string, error) {
   246  	m := int64(math.Ceil(float64(hugePages.Value()) / float64(divisor.Value())))
   247  	return strconv.FormatInt(m, 10), nil
   248  }
   249  
   250  // convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns
   251  // ceiling of the value.
   252  func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) {
   253  	m := int64(math.Ceil(float64(ephemeralStorage.Value()) / float64(divisor.Value())))
   254  	return strconv.FormatInt(m, 10), nil
   255  }
   256  
   257  var standardContainerResources = sets.NewString(
   258  	string(corev1.ResourceCPU),
   259  	string(corev1.ResourceMemory),
   260  	string(corev1.ResourceEphemeralStorage),
   261  )
   262  
   263  // IsStandardContainerResourceName returns true if the container can make a resource request
   264  // for the specified resource
   265  func IsStandardContainerResourceName(str string) bool {
   266  	return standardContainerResources.Has(str) || IsHugePageResourceName(corev1.ResourceName(str))
   267  }
   268  
   269  // IsHugePageResourceName returns true if the resource name has the huge page
   270  // resource prefix.
   271  func IsHugePageResourceName(name corev1.ResourceName) bool {
   272  	return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
   273  }
   274  

View as plain text