diff --git a/hack/.linted_packages b/hack/.linted_packages index 7d7e85a0202..2de9bcb3e50 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -178,6 +178,7 @@ pkg/controller/volume/attachdetach/reconciler pkg/conversion pkg/conversion/queryparams pkg/credentialprovider/aws +pkg/fieldpath pkg/fields pkg/hyperkube pkg/kubelet/api diff --git a/pkg/api/resource_helpers.go b/pkg/api/resource_helpers.go index 88d0f80d769..74da82ef622 100644 --- a/pkg/api/resource_helpers.go +++ b/pkg/api/resource_helpers.go @@ -17,6 +17,9 @@ limitations under the License. package api import ( + "fmt" + "math" + "strconv" "time" "k8s.io/apimachinery/pkg/api/resource" @@ -227,3 +230,41 @@ func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, li } 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 +} diff --git a/pkg/api/v1/BUILD b/pkg/api/v1/BUILD index 69f5da9f4f1..de91276d331 100644 --- a/pkg/api/v1/BUILD +++ b/pkg/api/v1/BUILD @@ -86,6 +86,7 @@ go_test( library = ":go_default_library", tags = ["automanaged"], deps = [ + "//vendor:github.com/stretchr/testify/assert", "//vendor:k8s.io/apimachinery/pkg/api/equality", "//vendor:k8s.io/apimachinery/pkg/api/resource", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", diff --git a/pkg/api/v1/resource_helpers.go b/pkg/api/v1/resource_helpers.go index ec842327626..cc769d12eb7 100644 --- a/pkg/api/v1/resource_helpers.go +++ b/pkg/api/v1/resource_helpers.go @@ -17,10 +17,14 @@ limitations under the License. package v1 import ( + "fmt" + "math" + "strconv" "time" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/api" ) // Returns string version of ResourceName. @@ -255,3 +259,100 @@ func GetResourceRequest(pod *Pod, resource ResourceName) int64 { } 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() + } + } + } +} diff --git a/pkg/api/v1/resource_helpers_test.go b/pkg/api/v1/resource_helpers_test.go index cadbc49ea1e..b22f91bae05 100644 --- a/pkg/api/v1/resource_helpers_test.go +++ b/pkg/api/v1/resource_helpers_test.go @@ -20,6 +20,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/api/resource" 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, + }, + }, + }, + } +} diff --git a/pkg/fieldpath/BUILD b/pkg/fieldpath/BUILD index 77fab5b9d69..7e2e01103ff 100644 --- a/pkg/fieldpath/BUILD +++ b/pkg/fieldpath/BUILD @@ -15,12 +15,7 @@ go_library( "fieldpath.go", ], tags = ["automanaged"], - deps = [ - "//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", - ], + deps = ["//vendor:k8s.io/apimachinery/pkg/api/meta"], ) go_test( @@ -30,8 +25,6 @@ go_test( tags = ["automanaged"], deps = [ "//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", ], ) diff --git a/pkg/fieldpath/fieldpath.go b/pkg/fieldpath/fieldpath.go index 9ddfa4ca705..4001da7997c 100644 --- a/pkg/fieldpath/fieldpath.go +++ b/pkg/fieldpath/fieldpath.go @@ -18,14 +18,9 @@ package fieldpath import ( "fmt" - "math" - "strconv" "strings" "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. @@ -60,126 +55,3 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) 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() - } - } - } -} diff --git a/pkg/fieldpath/fieldpath_test.go b/pkg/fieldpath/fieldpath_test.go index 3e49a3d672e..b91719d719f 100644 --- a/pkg/fieldpath/fieldpath_test.go +++ b/pkg/fieldpath/fieldpath_test.go @@ -20,9 +20,6 @@ import ( "strings" "testing" - "github.com/stretchr/testify/assert" - - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/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) - } - } -} diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 6cc135ad51f..e511021b94f 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -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) { containerName := fs.ContainerName if len(containerName) == 0 { - return fieldpath.ExtractContainerResourceValue(fs, container) + return v1.ExtractContainerResourceValue(fs, container) } else { - return fieldpath.ExtractResourceValueByContainerName(fs, pod, containerName) + return v1.ExtractResourceValueByContainerName(fs, pod, containerName) } } diff --git a/pkg/kubelet/kubelet_resources.go b/pkg/kubelet/kubelet_resources.go index 21fecbdee8c..98c47f353a5 100644 --- a/pkg/kubelet/kubelet_resources.go +++ b/pkg/kubelet/kubelet_resources.go @@ -23,7 +23,6 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/fieldpath" ) // 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") } for idx := range outputPod.Spec.Containers { - fieldpath.MergeContainerResourceLimits(&outputPod.Spec.Containers[idx], allocatable) + v1.MergeContainerResourceLimits(&outputPod.Spec.Containers[idx], allocatable) } var outputContainer *v1.Container @@ -66,7 +65,7 @@ func (kl *Kubelet) defaultPodLimitsForDownwardApi(pod *v1.Pod, container *v1.Con if !ok { 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 } diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go index c50db6968b9..1be0c0f9906 100644 --- a/pkg/printers/internalversion/describe.go +++ b/pkg/printers/internalversion/describe.go @@ -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) case e.ValueFrom.ResourceFieldRef != nil: - valueFrom, err := fieldpath.InternalExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container) + valueFrom, err := api.ExtractContainerResourceValue(e.ValueFrom.ResourceFieldRef, &container) if err != nil { valueFrom = "" } diff --git a/pkg/volume/downwardapi/downwardapi.go b/pkg/volume/downwardapi/downwardapi.go index 409aa56ac53..3ba7d6ed42c 100644 --- a/pkg/volume/downwardapi/downwardapi.go +++ b/pkg/volume/downwardapi/downwardapi.go @@ -244,7 +244,7 @@ func CollectData(items []v1.DownwardAPIVolumeFile, pod *v1.Pod, host volume.Volu nodeAllocatable, err := host.GetNodeAllocatable() if err != nil { 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()) errlist = append(errlist, err) } else { diff --git a/staging/src/k8s.io/client-go/pkg/api/resource_helpers.go b/staging/src/k8s.io/client-go/pkg/api/resource_helpers.go index 88d0f80d769..74da82ef622 100644 --- a/staging/src/k8s.io/client-go/pkg/api/resource_helpers.go +++ b/staging/src/k8s.io/client-go/pkg/api/resource_helpers.go @@ -17,6 +17,9 @@ limitations under the License. package api import ( + "fmt" + "math" + "strconv" "time" "k8s.io/apimachinery/pkg/api/resource" @@ -227,3 +230,41 @@ func PodRequestsAndLimits(pod *Pod) (reqs map[ResourceName]resource.Quantity, li } 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 +} diff --git a/staging/src/k8s.io/client-go/pkg/api/v1/resource_helpers.go b/staging/src/k8s.io/client-go/pkg/api/v1/resource_helpers.go index ec842327626..b80efd18b0d 100644 --- a/staging/src/k8s.io/client-go/pkg/api/v1/resource_helpers.go +++ b/staging/src/k8s.io/client-go/pkg/api/v1/resource_helpers.go @@ -17,10 +17,14 @@ limitations under the License. package v1 import ( + "fmt" + "math" + "strconv" "time" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/pkg/api" ) // Returns string version of ResourceName. @@ -255,3 +259,100 @@ func GetResourceRequest(pod *Pod, resource ResourceName) int64 { } 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() + } + } + } +}