When limits are not set, use capacity as limits in downward API for resources.

Signed-off-by: Vishnu kannan <vishnuk@google.com>
This commit is contained in:
Vishnu kannan 2016-06-08 16:42:10 -07:00 committed by derekwaynecarr
parent 939ad4115a
commit afdd9ea262
5 changed files with 282 additions and 3 deletions

View File

@ -20,7 +20,10 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
)
func TestExtractFieldPathAsString(t *testing.T) {
@ -115,3 +118,119 @@ func TestExtractFieldPathAsString(t *testing.T) {
}
}
}
func getPod(cname, cpuRequest, cpuLimit, memoryRequest, memoryLimit string) *api.Pod {
resources := api.ResourceRequirements{
Limits: make(api.ResourceList),
Requests: make(api.ResourceList),
}
if cpuLimit != "" {
resources.Limits[api.ResourceCPU] = resource.MustParse(cpuLimit)
}
if memoryLimit != "" {
resources.Limits[api.ResourceMemory] = resource.MustParse(memoryLimit)
}
if cpuRequest != "" {
resources.Requests[api.ResourceCPU] = resource.MustParse(cpuRequest)
}
if memoryRequest != "" {
resources.Requests[api.ResourceMemory] = resource.MustParse(memoryRequest)
}
return &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: cname,
Resources: resources,
},
},
},
}
}
func TestExtractResourceValue(t *testing.T) {
cases := []struct {
fs *api.ResourceFieldSelector
pod *api.Pod
cName string
expectedValue string
expectedError error
}{
{
fs: &api.ResourceFieldSelector{
Resource: "limits.cpu",
},
cName: "foo",
pod: getPod("foo", "", "9", "", ""),
expectedValue: "9",
},
{
fs: &api.ResourceFieldSelector{
Resource: "requests.cpu",
},
cName: "foo",
pod: getPod("foo", "", "", "", ""),
expectedValue: "0",
},
{
fs: &api.ResourceFieldSelector{
Resource: "requests.cpu",
},
cName: "foo",
pod: getPod("foo", "8", "", "", ""),
expectedValue: "8",
},
{
fs: &api.ResourceFieldSelector{
Resource: "requests.cpu",
},
cName: "foo",
pod: getPod("foo", "100m", "", "", ""),
expectedValue: "1",
},
{
fs: &api.ResourceFieldSelector{
Resource: "requests.cpu",
Divisor: resource.MustParse("100m"),
},
cName: "foo",
pod: getPod("foo", "1200m", "", "", ""),
expectedValue: "12",
},
{
fs: &api.ResourceFieldSelector{
Resource: "requests.memory",
},
cName: "foo",
pod: getPod("foo", "", "", "100Mi", ""),
expectedValue: "104857600",
},
{
fs: &api.ResourceFieldSelector{
Resource: "requests.memory",
Divisor: resource.MustParse("1Mi"),
},
cName: "foo",
pod: getPod("foo", "", "", "100Mi", "1Gi"),
expectedValue: "100",
},
{
fs: &api.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)
}
}
}

View File

@ -831,6 +831,8 @@ type Kubelet struct {
// should manage attachment/detachment of volumes scheduled to this node,
// and disable kubelet from executing any attach/detach operations
enableControllerAttachDetach bool
lastUpdatedNodeObject atomic.Value
}
// Validate given node IP belongs to the current host
@ -1143,6 +1145,10 @@ func (kl *Kubelet) registerWithApiserver() {
glog.Errorf("Unable to construct api.Node object for kubelet: %v", err)
continue
}
// Cache the node object.
kl.lastUpdatedNodeObject.Store(node)
glog.V(2).Infof("Attempting to register node %s", node.Name)
if _, err := kl.kubeClient.Core().Nodes().Create(node); err != nil {
if !apierrors.IsAlreadyExists(err) {
@ -1554,7 +1560,11 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Contain
return result, err
}
case envVar.ValueFrom.ResourceFieldRef != nil:
runtimeVal, err = containerResourceRuntimeValue(envVar.ValueFrom.ResourceFieldRef, pod, container)
defaultedPod, err := kl.defaultPodLimitsForDownwardApi(pod)
if err != nil {
return result, err
}
runtimeVal, err = containerResourceRuntimeValue(envVar.ValueFrom.ResourceFieldRef, defaultedPod, container)
if err != nil {
return result, err
}
@ -1894,7 +1904,12 @@ func (kl *Kubelet) syncPod(o syncPodOptions) error {
}
// Mount volumes and update the volume manager
podVolumes, err := kl.mountExternalVolumes(pod)
// Default limits for containers here to have downward API expose user-friendly limits to pods.
defaultedPod, err := kl.defaultPodLimitsForDownwardApi(pod)
if err != nil {
return err
}
podVolumes, err := kl.mountExternalVolumes(defaultedPod)
if err != nil {
ref, errGetRef := api.GetReference(pod)
if errGetRef == nil && ref != nil {
@ -3507,6 +3522,10 @@ func (kl *Kubelet) tryUpdateNodeStatus() error {
}
// Update the current status on the API server
_, err = kl.kubeClient.Core().Nodes().UpdateStatus(node)
if err == nil {
// store recently updated node information.
kl.lastUpdatedNodeObject.Store(node)
}
return err
}

View File

@ -278,7 +278,7 @@ func newTestKubeletWithImageList(t *testing.T, imageList []kubecontainer.Image)
}
kubelet.evictionManager = evictionManager
kubelet.AddPodAdmitHandler(evictionAdmitHandler)
kubelet.lastUpdatedNodeObject.Store(&api.Node{})
return &TestKubelet{kubelet, fakeRuntime, mockCadvisor, fakeKubeClient, fakeMirrorClient, fakeClock, nil}
}

52
pkg/kubelet/resources.go Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubelet
import (
"fmt"
"k8s.io/kubernetes/pkg/api"
)
func (kl *Kubelet) defaultPodLimitsForDownwardApi(pod *api.Pod) (*api.Pod, error) {
capacity := make(api.ResourceList)
lastUpdatedNodeObject := kl.lastUpdatedNodeObject.Load()
if lastUpdatedNodeObject == nil {
return nil, fmt.Errorf("Failed to find node object in cache. Expected a non-nil object in the cache.")
} else {
capacity = lastUpdatedNodeObject.(*api.Node).Status.Capacity
}
podCopy, err := api.Scheme.Copy(pod)
if err != nil {
return nil, fmt.Errorf("failed to perform a deep copy of pod object. Error: %v", err)
}
pod = podCopy.(*api.Pod)
for idx, c := range pod.Spec.Containers {
for _, resource := range []api.ResourceName{api.ResourceCPU, api.ResourceMemory} {
if quantity, exists := c.Resources.Limits[resource]; !exists || quantity.IsZero() {
if cap, exists := capacity[resource]; exists {
if pod.Spec.Containers[idx].Resources.Limits == nil {
pod.Spec.Containers[idx].Resources.Limits = make(api.ResourceList)
}
pod.Spec.Containers[idx].Resources.Limits[resource] = cap
}
}
}
}
return pod, nil
}

View File

@ -0,0 +1,89 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package kubelet
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
)
func TestPodResourceLimitsDefaulting(t *testing.T) {
tk := newTestKubelet(t)
node := &api.Node{
Status: api.NodeStatus{
Capacity: api.ResourceList{
api.ResourceCPU: resource.MustParse("10"),
api.ResourceMemory: resource.MustParse("10Gi"),
},
},
}
tk.kubelet.lastUpdatedNodeObject.Store(node)
cases := []struct {
pod *api.Pod
expected *api.Pod
}{
{
pod: getPod("0", "0"),
expected: getPod("10", "10Gi"),
},
{
pod: getPod("1", "0"),
expected: getPod("1", "10Gi"),
},
{
pod: getPod("", ""),
expected: getPod("10", "10Gi"),
},
{
pod: getPod("0", "1Mi"),
expected: getPod("10", "1Mi"),
},
}
as := assert.New(t)
for idx, tc := range cases {
actual, err := tk.kubelet.defaultPodLimitsForDownwardApi(tc.pod)
as.Nil(err, "failed to default pod limits: %v", err)
as.Equal(tc.expected, actual, "test case [%d] failed. Expected: %+v, Got: %+v", idx, tc.expected, actual)
}
}
func getPod(cpuLimit, memoryLimit string) *api.Pod {
resources := api.ResourceRequirements{}
if cpuLimit != "" && memoryLimit != "" {
resources.Limits = make(api.ResourceList)
}
if cpuLimit != "" {
resources.Limits[api.ResourceCPU] = resource.MustParse(cpuLimit)
}
if memoryLimit != "" {
resources.Limits[api.ResourceMemory] = resource.MustParse(memoryLimit)
}
return &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Resources: resources,
},
},
},
}
}