mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +00:00
Merge pull request #39678 from resouer/extract-resource
Automatic merge from submit-queue (batch tested with PRs 41775, 39678, 42629, 42524, 43028) Extract resources functions belongs to api/util Address: extract kubelet resources functions belongs to `pkg/api/v1/resource_helpers.go`
This commit is contained in:
commit
c8f90171e4
@ -178,6 +178,7 @@ pkg/controller/volume/attachdetach/reconciler
|
|||||||
pkg/conversion
|
pkg/conversion
|
||||||
pkg/conversion/queryparams
|
pkg/conversion/queryparams
|
||||||
pkg/credentialprovider/aws
|
pkg/credentialprovider/aws
|
||||||
|
pkg/fieldpath
|
||||||
pkg/fields
|
pkg/fields
|
||||||
pkg/hyperkube
|
pkg/hyperkube
|
||||||
pkg/kubelet/api
|
pkg/kubelet/api
|
||||||
|
@ -17,6 +17,9 @@ limitations under the License.
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
@ -227,3 +230,41 @@ func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, li
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractContainerResourceValue extracts the value of a resource
|
||||||
|
// in an already known container
|
||||||
|
func ExtractContainerResourceValue(fs *ResourceFieldSelector, container *Container) (string, error) {
|
||||||
|
divisor := resource.Quantity{}
|
||||||
|
if divisor.Cmp(fs.Divisor) == 0 {
|
||||||
|
divisor = resource.MustParse("1")
|
||||||
|
} else {
|
||||||
|
divisor = fs.Divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fs.Resource {
|
||||||
|
case "limits.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
|
||||||
|
case "limits.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
|
||||||
|
case "requests.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
|
||||||
|
case "requests.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("unsupported container resource : %v", fs.Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceCPUToString converts cpu value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
|
||||||
|
return strconv.FormatInt(c, 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceMemoryToString converts memory value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
|
||||||
|
return strconv.FormatInt(m, 10), nil
|
||||||
|
}
|
||||||
|
@ -86,6 +86,7 @@ go_test(
|
|||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//vendor:github.com/stretchr/testify/assert",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/api/equality",
|
"//vendor:k8s.io/apimachinery/pkg/api/equality",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/api/resource",
|
"//vendor:k8s.io/apimachinery/pkg/api/resource",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
|
@ -17,10 +17,14 @@ limitations under the License.
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns string version of ResourceName.
|
// Returns string version of ResourceName.
|
||||||
@ -255,3 +259,100 @@ func GetResourceRequest(pod *Pod, resource ResourceName) int64 {
|
|||||||
}
|
}
|
||||||
return totalResources
|
return totalResources
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractResourceValueByContainerName extracts the value of a resource
|
||||||
|
// by providing container name
|
||||||
|
func ExtractResourceValueByContainerName(fs *ResourceFieldSelector, pod *Pod, containerName string) (string, error) {
|
||||||
|
container, err := findContainerInPod(pod, containerName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ExtractContainerResourceValue(fs, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractResourceValueByContainerNameAndNodeAllocatable extracts the value of a resource
|
||||||
|
// by providing container name and node allocatable
|
||||||
|
func ExtractResourceValueByContainerNameAndNodeAllocatable(fs *ResourceFieldSelector, pod *Pod, containerName string, nodeAllocatable ResourceList) (string, error) {
|
||||||
|
realContainer, err := findContainerInPod(pod, containerName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCopy, err := api.Scheme.DeepCopy(realContainer)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to perform a deep copy of container object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
container, ok := containerCopy.(*Container)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("unexpected type returned from deep copy of container object")
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeContainerResourceLimits(container, nodeAllocatable)
|
||||||
|
|
||||||
|
return ExtractContainerResourceValue(fs, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractContainerResourceValue extracts the value of a resource
|
||||||
|
// in an already known container
|
||||||
|
func ExtractContainerResourceValue(fs *ResourceFieldSelector, container *Container) (string, error) {
|
||||||
|
divisor := resource.Quantity{}
|
||||||
|
if divisor.Cmp(fs.Divisor) == 0 {
|
||||||
|
divisor = resource.MustParse("1")
|
||||||
|
} else {
|
||||||
|
divisor = fs.Divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fs.Resource {
|
||||||
|
case "limits.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
|
||||||
|
case "limits.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
|
||||||
|
case "requests.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
|
||||||
|
case "requests.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceCPUToString converts cpu value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
|
||||||
|
return strconv.FormatInt(c, 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceMemoryToString converts memory value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
|
||||||
|
return strconv.FormatInt(m, 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findContainerInPod finds a container by its name in the provided pod
|
||||||
|
func findContainerInPod(pod *Pod, containerName string) (*Container, error) {
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
if container.Name == containerName {
|
||||||
|
return &container, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("container %s not found", containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeContainerResourceLimits checks if a limit is applied for
|
||||||
|
// the container, and if not, it sets the limit to the passed resource list.
|
||||||
|
func MergeContainerResourceLimits(container *Container,
|
||||||
|
allocatable ResourceList) {
|
||||||
|
if container.Resources.Limits == nil {
|
||||||
|
container.Resources.Limits = make(ResourceList)
|
||||||
|
}
|
||||||
|
for _, resource := range []ResourceName{ResourceCPU, ResourceMemory} {
|
||||||
|
if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() {
|
||||||
|
if cap, exists := allocatable[resource]; exists {
|
||||||
|
container.Resources.Limits[resource] = *cap.Copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
@ -118,3 +120,119 @@ func TestIsPodAvailable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractResourceValue(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
fs *ResourceFieldSelector
|
||||||
|
pod *Pod
|
||||||
|
cName string
|
||||||
|
expectedValue string
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "limits.cpu",
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "", "9", "", ""),
|
||||||
|
expectedValue: "9",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "requests.cpu",
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "", "", "", ""),
|
||||||
|
expectedValue: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "requests.cpu",
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "8", "", "", ""),
|
||||||
|
expectedValue: "8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "requests.cpu",
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "100m", "", "", ""),
|
||||||
|
expectedValue: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "requests.cpu",
|
||||||
|
Divisor: resource.MustParse("100m"),
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "1200m", "", "", ""),
|
||||||
|
expectedValue: "12",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "requests.memory",
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "", "", "100Mi", ""),
|
||||||
|
expectedValue: "104857600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "requests.memory",
|
||||||
|
Divisor: resource.MustParse("1Mi"),
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "", "", "100Mi", "1Gi"),
|
||||||
|
expectedValue: "100",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fs: &ResourceFieldSelector{
|
||||||
|
Resource: "limits.memory",
|
||||||
|
},
|
||||||
|
cName: "foo",
|
||||||
|
pod: getPod("foo", "", "", "10Mi", "100Mi"),
|
||||||
|
expectedValue: "104857600",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
as := assert.New(t)
|
||||||
|
for idx, tc := range cases {
|
||||||
|
actual, err := ExtractResourceValueByContainerName(tc.fs, tc.pod, tc.cName)
|
||||||
|
if tc.expectedError != nil {
|
||||||
|
as.Equal(tc.expectedError, err, "expected test case [%d] to fail with error %v; got %v", idx, tc.expectedError, err)
|
||||||
|
} else {
|
||||||
|
as.Nil(err, "expected test case [%d] to not return an error; got %v", idx, err)
|
||||||
|
as.Equal(tc.expectedValue, actual, "expected test case [%d] to return %q; got %q instead", idx, tc.expectedValue, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPod(cname, cpuRequest, cpuLimit, memoryRequest, memoryLimit string) *Pod {
|
||||||
|
resources := ResourceRequirements{
|
||||||
|
Limits: make(ResourceList),
|
||||||
|
Requests: make(ResourceList),
|
||||||
|
}
|
||||||
|
if cpuLimit != "" {
|
||||||
|
resources.Limits[ResourceCPU] = resource.MustParse(cpuLimit)
|
||||||
|
}
|
||||||
|
if memoryLimit != "" {
|
||||||
|
resources.Limits[ResourceMemory] = resource.MustParse(memoryLimit)
|
||||||
|
}
|
||||||
|
if cpuRequest != "" {
|
||||||
|
resources.Requests[ResourceCPU] = resource.MustParse(cpuRequest)
|
||||||
|
}
|
||||||
|
if memoryRequest != "" {
|
||||||
|
resources.Requests[ResourceMemory] = resource.MustParse(memoryRequest)
|
||||||
|
}
|
||||||
|
return &Pod{
|
||||||
|
Spec: PodSpec{
|
||||||
|
Containers: []Container{
|
||||||
|
{
|
||||||
|
Name: cname,
|
||||||
|
Resources: resources,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,12 +15,7 @@ go_library(
|
|||||||
"fieldpath.go",
|
"fieldpath.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = ["//vendor:k8s.io/apimachinery/pkg/api/meta"],
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/api/v1:go_default_library",
|
|
||||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
|
||||||
"//vendor:k8s.io/apimachinery/pkg/api/resource",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
@ -30,8 +25,6 @@ go_test(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/v1:go_default_library",
|
||||||
"//vendor:github.com/stretchr/testify/assert",
|
|
||||||
"//vendor:k8s.io/apimachinery/pkg/api/resource",
|
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -18,14 +18,9 @@ package fieldpath
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatMap formats map[string]string to a string.
|
// FormatMap formats map[string]string to a string.
|
||||||
@ -60,126 +55,3 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error)
|
|||||||
|
|
||||||
return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
|
return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move the functions below to pkg/api/util/resources
|
|
||||||
// ExtractResourceValueByContainerName extracts the value of a resource
|
|
||||||
// by providing container name
|
|
||||||
func ExtractResourceValueByContainerName(fs *v1.ResourceFieldSelector, pod *v1.Pod, containerName string) (string, error) {
|
|
||||||
container, err := findContainerInPod(pod, containerName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return ExtractContainerResourceValue(fs, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractResourceValueByContainerNameAndNodeAllocatable extracts the value of a resource
|
|
||||||
// by providing container name and node allocatable
|
|
||||||
func ExtractResourceValueByContainerNameAndNodeAllocatable(fs *v1.ResourceFieldSelector, pod *v1.Pod, containerName string, nodeAllocatable v1.ResourceList) (string, error) {
|
|
||||||
realContainer, err := findContainerInPod(pod, containerName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
containerCopy, err := api.Scheme.DeepCopy(realContainer)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to perform a deep copy of container object: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
container, ok := containerCopy.(*v1.Container)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("unexpected type returned from deep copy of container object")
|
|
||||||
}
|
|
||||||
|
|
||||||
MergeContainerResourceLimits(container, nodeAllocatable)
|
|
||||||
|
|
||||||
return ExtractContainerResourceValue(fs, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractContainerResourceValue extracts the value of a resource
|
|
||||||
// in an already known container
|
|
||||||
func ExtractContainerResourceValue(fs *v1.ResourceFieldSelector, container *v1.Container) (string, error) {
|
|
||||||
divisor := resource.Quantity{}
|
|
||||||
if divisor.Cmp(fs.Divisor) == 0 {
|
|
||||||
divisor = resource.MustParse("1")
|
|
||||||
} else {
|
|
||||||
divisor = fs.Divisor
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fs.Resource {
|
|
||||||
case "limits.cpu":
|
|
||||||
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
|
|
||||||
case "limits.memory":
|
|
||||||
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
|
|
||||||
case "requests.cpu":
|
|
||||||
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
|
|
||||||
case "requests.memory":
|
|
||||||
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove this duplicate
|
|
||||||
// InternalExtractContainerResourceValue extracts the value of a resource
|
|
||||||
// in an already known container
|
|
||||||
func InternalExtractContainerResourceValue(fs *api.ResourceFieldSelector, container *api.Container) (string, error) {
|
|
||||||
divisor := resource.Quantity{}
|
|
||||||
if divisor.Cmp(fs.Divisor) == 0 {
|
|
||||||
divisor = resource.MustParse("1")
|
|
||||||
} else {
|
|
||||||
divisor = fs.Divisor
|
|
||||||
}
|
|
||||||
|
|
||||||
switch fs.Resource {
|
|
||||||
case "limits.cpu":
|
|
||||||
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
|
|
||||||
case "limits.memory":
|
|
||||||
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
|
|
||||||
case "requests.cpu":
|
|
||||||
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
|
|
||||||
case "requests.memory":
|
|
||||||
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("unsupported container resource : %v", fs.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
// findContainerInPod finds a container by its name in the provided pod
|
|
||||||
func findContainerInPod(pod *v1.Pod, containerName string) (*v1.Container, error) {
|
|
||||||
for _, container := range pod.Spec.Containers {
|
|
||||||
if container.Name == containerName {
|
|
||||||
return &container, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("container %s not found", containerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertResourceCPUToString converts cpu value to the format of divisor and returns
|
|
||||||
// ceiling of the value.
|
|
||||||
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
|
|
||||||
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
|
|
||||||
return strconv.FormatInt(c, 10), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertResourceMemoryToString converts memory value to the format of divisor and returns
|
|
||||||
// ceiling of the value.
|
|
||||||
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
|
|
||||||
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
|
|
||||||
return strconv.FormatInt(m, 10), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeContainerResourceLimits checks if a limit is applied for
|
|
||||||
// the container, and if not, it sets the limit to the passed resource list.
|
|
||||||
func MergeContainerResourceLimits(container *v1.Container,
|
|
||||||
allocatable v1.ResourceList) {
|
|
||||||
if container.Resources.Limits == nil {
|
|
||||||
container.Resources.Limits = make(v1.ResourceList)
|
|
||||||
}
|
|
||||||
for _, resource := range []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory} {
|
|
||||||
if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() {
|
|
||||||
if cap, exists := allocatable[resource]; exists {
|
|
||||||
container.Resources.Limits[resource] = *cap.Copy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -20,9 +20,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
)
|
)
|
||||||
@ -119,119 +116,3 @@ func TestExtractFieldPathAsString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPod(cname, cpuRequest, cpuLimit, memoryRequest, memoryLimit string) *v1.Pod {
|
|
||||||
resources := v1.ResourceRequirements{
|
|
||||||
Limits: make(v1.ResourceList),
|
|
||||||
Requests: make(v1.ResourceList),
|
|
||||||
}
|
|
||||||
if cpuLimit != "" {
|
|
||||||
resources.Limits[v1.ResourceCPU] = resource.MustParse(cpuLimit)
|
|
||||||
}
|
|
||||||
if memoryLimit != "" {
|
|
||||||
resources.Limits[v1.ResourceMemory] = resource.MustParse(memoryLimit)
|
|
||||||
}
|
|
||||||
if cpuRequest != "" {
|
|
||||||
resources.Requests[v1.ResourceCPU] = resource.MustParse(cpuRequest)
|
|
||||||
}
|
|
||||||
if memoryRequest != "" {
|
|
||||||
resources.Requests[v1.ResourceMemory] = resource.MustParse(memoryRequest)
|
|
||||||
}
|
|
||||||
return &v1.Pod{
|
|
||||||
Spec: v1.PodSpec{
|
|
||||||
Containers: []v1.Container{
|
|
||||||
{
|
|
||||||
Name: cname,
|
|
||||||
Resources: resources,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtractResourceValue(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
fs *v1.ResourceFieldSelector
|
|
||||||
pod *v1.Pod
|
|
||||||
cName string
|
|
||||||
expectedValue string
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "limits.cpu",
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "", "9", "", ""),
|
|
||||||
expectedValue: "9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "requests.cpu",
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "", "", "", ""),
|
|
||||||
expectedValue: "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "requests.cpu",
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "8", "", "", ""),
|
|
||||||
expectedValue: "8",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "requests.cpu",
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "100m", "", "", ""),
|
|
||||||
expectedValue: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "requests.cpu",
|
|
||||||
Divisor: resource.MustParse("100m"),
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "1200m", "", "", ""),
|
|
||||||
expectedValue: "12",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "requests.memory",
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "", "", "100Mi", ""),
|
|
||||||
expectedValue: "104857600",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "requests.memory",
|
|
||||||
Divisor: resource.MustParse("1Mi"),
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "", "", "100Mi", "1Gi"),
|
|
||||||
expectedValue: "100",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fs: &v1.ResourceFieldSelector{
|
|
||||||
Resource: "limits.memory",
|
|
||||||
},
|
|
||||||
cName: "foo",
|
|
||||||
pod: getPod("foo", "", "", "10Mi", "100Mi"),
|
|
||||||
expectedValue: "104857600",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
as := assert.New(t)
|
|
||||||
for idx, tc := range cases {
|
|
||||||
actual, err := ExtractResourceValueByContainerName(tc.fs, tc.pod, tc.cName)
|
|
||||||
if tc.expectedError != nil {
|
|
||||||
as.Equal(tc.expectedError, err, "expected test case [%d] to fail with error %v; got %v", idx, tc.expectedError, err)
|
|
||||||
} else {
|
|
||||||
as.Nil(err, "expected test case [%d] to not return an error; got %v", idx, err)
|
|
||||||
as.Equal(tc.expectedValue, actual, "expected test case [%d] to return %q; got %q instead", idx, tc.expectedValue, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -654,9 +654,9 @@ func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod
|
|||||||
func containerResourceRuntimeValue(fs *v1.ResourceFieldSelector, pod *v1.Pod, container *v1.Container) (string, error) {
|
func containerResourceRuntimeValue(fs *v1.ResourceFieldSelector, pod *v1.Pod, container *v1.Container) (string, error) {
|
||||||
containerName := fs.ContainerName
|
containerName := fs.ContainerName
|
||||||
if len(containerName) == 0 {
|
if len(containerName) == 0 {
|
||||||
return fieldpath.ExtractContainerResourceValue(fs, container)
|
return v1.ExtractContainerResourceValue(fs, container)
|
||||||
} else {
|
} else {
|
||||||
return fieldpath.ExtractResourceValueByContainerName(fs, pod, containerName)
|
return v1.ExtractResourceValueByContainerName(fs, pod, containerName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
"k8s.io/kubernetes/pkg/fieldpath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultPodLimitsForDownwardApi copies the input pod, and optional container,
|
// defaultPodLimitsForDownwardApi copies the input pod, and optional container,
|
||||||
@ -53,7 +52,7 @@ func (kl *Kubelet) defaultPodLimitsForDownwardApi(pod *v1.Pod, container *v1.Con
|
|||||||
return nil, nil, fmt.Errorf("unexpected type returned from deep copy of pod object")
|
return nil, nil, fmt.Errorf("unexpected type returned from deep copy of pod object")
|
||||||
}
|
}
|
||||||
for idx := range outputPod.Spec.Containers {
|
for idx := range outputPod.Spec.Containers {
|
||||||
fieldpath.MergeContainerResourceLimits(&outputPod.Spec.Containers[idx], allocatable)
|
v1.MergeContainerResourceLimits(&outputPod.Spec.Containers[idx], allocatable)
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputContainer *v1.Container
|
var outputContainer *v1.Container
|
||||||
@ -66,7 +65,7 @@ func (kl *Kubelet) defaultPodLimitsForDownwardApi(pod *v1.Pod, container *v1.Con
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, fmt.Errorf("unexpected type returned from deep copy of container object")
|
return nil, nil, fmt.Errorf("unexpected type returned from deep copy of container object")
|
||||||
}
|
}
|
||||||
fieldpath.MergeContainerResourceLimits(outputContainer, allocatable)
|
v1.MergeContainerResourceLimits(outputContainer, allocatable)
|
||||||
}
|
}
|
||||||
return outputPod, outputContainer, nil
|
return outputPod, outputContainer, nil
|
||||||
}
|
}
|
||||||
|
@ -1100,7 +1100,7 @@ func describeContainerEnvVars(container api.Container, resolverFn EnvVarResolver
|
|||||||
}
|
}
|
||||||
w.Write(LEVEL_3, "%s:\t%s (%s:%s)\n", e.Name, valueFrom, e.ValueFrom.FieldRef.APIVersion, e.ValueFrom.FieldRef.FieldPath)
|
w.Write(LEVEL_3, "%s:\t%s (%s:%s)\n", e.Name, valueFrom, e.ValueFrom.FieldRef.APIVersion, e.ValueFrom.FieldRef.FieldPath)
|
||||||
case e.ValueFrom.ResourceFieldRef != nil:
|
case e.ValueFrom.ResourceFieldRef != nil:
|
||||||
valueFrom, err := fieldpath.InternalExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container)
|
valueFrom, err := api.ExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
valueFrom = ""
|
valueFrom = ""
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,7 @@ func CollectData(items []v1.DownwardAPIVolumeFile, pod *v1.Pod, host volume.Volu
|
|||||||
nodeAllocatable, err := host.GetNodeAllocatable()
|
nodeAllocatable, err := host.GetNodeAllocatable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errlist = append(errlist, err)
|
errlist = append(errlist, err)
|
||||||
} else if values, err := fieldpath.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, pod, containerName, nodeAllocatable); err != nil {
|
} else if values, err := v1.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, pod, containerName, nodeAllocatable); err != nil {
|
||||||
glog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error())
|
glog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error())
|
||||||
errlist = append(errlist, err)
|
errlist = append(errlist, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -17,6 +17,9 @@ limitations under the License.
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
@ -227,3 +230,41 @@ func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, li
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractContainerResourceValue extracts the value of a resource
|
||||||
|
// in an already known container
|
||||||
|
func ExtractContainerResourceValue(fs *ResourceFieldSelector, container *Container) (string, error) {
|
||||||
|
divisor := resource.Quantity{}
|
||||||
|
if divisor.Cmp(fs.Divisor) == 0 {
|
||||||
|
divisor = resource.MustParse("1")
|
||||||
|
} else {
|
||||||
|
divisor = fs.Divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fs.Resource {
|
||||||
|
case "limits.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
|
||||||
|
case "limits.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
|
||||||
|
case "requests.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
|
||||||
|
case "requests.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("unsupported container resource : %v", fs.Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceCPUToString converts cpu value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
|
||||||
|
return strconv.FormatInt(c, 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceMemoryToString converts memory value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
|
||||||
|
return strconv.FormatInt(m, 10), nil
|
||||||
|
}
|
||||||
|
@ -17,10 +17,14 @@ limitations under the License.
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns string version of ResourceName.
|
// Returns string version of ResourceName.
|
||||||
@ -255,3 +259,100 @@ func GetResourceRequest(pod *Pod, resource ResourceName) int64 {
|
|||||||
}
|
}
|
||||||
return totalResources
|
return totalResources
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExtractResourceValueByContainerName extracts the value of a resource
|
||||||
|
// by providing container name
|
||||||
|
func ExtractResourceValueByContainerName(fs *ResourceFieldSelector, pod *Pod, containerName string) (string, error) {
|
||||||
|
container, err := findContainerInPod(pod, containerName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ExtractContainerResourceValue(fs, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractResourceValueByContainerNameAndNodeAllocatable extracts the value of a resource
|
||||||
|
// by providing container name and node allocatable
|
||||||
|
func ExtractResourceValueByContainerNameAndNodeAllocatable(fs *ResourceFieldSelector, pod *Pod, containerName string, nodeAllocatable ResourceList) (string, error) {
|
||||||
|
realContainer, err := findContainerInPod(pod, containerName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
containerCopy, err := api.Scheme.DeepCopy(realContainer)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to perform a deep copy of container object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
container, ok := containerCopy.(*Container)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("unexpected type returned from deep copy of container object")
|
||||||
|
}
|
||||||
|
|
||||||
|
MergeContainerResourceLimits(container, nodeAllocatable)
|
||||||
|
|
||||||
|
return ExtractContainerResourceValue(fs, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractContainerResourceValue extracts the value of a resource
|
||||||
|
// in an already known container
|
||||||
|
func ExtractContainerResourceValue(fs *ResourceFieldSelector, container *Container) (string, error) {
|
||||||
|
divisor := resource.Quantity{}
|
||||||
|
if divisor.Cmp(fs.Divisor) == 0 {
|
||||||
|
divisor = resource.MustParse("1")
|
||||||
|
} else {
|
||||||
|
divisor = fs.Divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fs.Resource {
|
||||||
|
case "limits.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
|
||||||
|
case "limits.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
|
||||||
|
case "requests.cpu":
|
||||||
|
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
|
||||||
|
case "requests.memory":
|
||||||
|
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceCPUToString converts cpu value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
|
||||||
|
return strconv.FormatInt(c, 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertResourceMemoryToString converts memory value to the format of divisor and returns
|
||||||
|
// ceiling of the value.
|
||||||
|
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
|
||||||
|
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
|
||||||
|
return strconv.FormatInt(m, 10), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findContainerInPod finds a container by its name in the provided pod
|
||||||
|
func findContainerInPod(pod *Pod, containerName string) (*Container, error) {
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
if container.Name == containerName {
|
||||||
|
return &container, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("container %s not found", containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeContainerResourceLimits checks if a limit is applied for
|
||||||
|
// the container, and if not, it sets the limit to the passed resource list.
|
||||||
|
func MergeContainerResourceLimits(container *Container,
|
||||||
|
allocatable ResourceList) {
|
||||||
|
if container.Resources.Limits == nil {
|
||||||
|
container.Resources.Limits = make(ResourceList)
|
||||||
|
}
|
||||||
|
for _, resource := range []ResourceName{ResourceCPU, ResourceMemory} {
|
||||||
|
if quantity, exists := container.Resources.Limits[resource]; !exists || quantity.IsZero() {
|
||||||
|
if cap, exists := allocatable[resource]; exists {
|
||||||
|
container.Resources.Limits[resource] = *cap.Copy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user