Update limit ranging to handle init containers

pull/6/head
Clayton Coleman 2016-04-08 11:20:33 -04:00
parent 1b6591312d
commit f2008152f4
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
2 changed files with 150 additions and 37 deletions

View File

@ -208,13 +208,8 @@ func defaultContainerResourceRequirements(limitRange *api.LimitRange) api.Resour
return requirements return requirements
} }
// mergePodResourceRequirements merges enumerated requirements with default requirements // mergeContainerResources handles defaulting all of the resources on a container.
// it annotates the pod with information about what requirements were modified func mergeContainerResources(container *api.Container, defaultRequirements *api.ResourceRequirements, annotationPrefix string, annotations []string) []string {
func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) {
annotations := []string{}
for i := range pod.Spec.Containers {
container := &pod.Spec.Containers[i]
setRequests := []string{} setRequests := []string{}
setLimits := []string{} setLimits := []string{}
if container.Resources.Limits == nil { if container.Resources.Limits == nil {
@ -239,14 +234,28 @@ func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.Resourc
} }
if len(setRequests) > 0 { if len(setRequests) > 0 {
sort.Strings(setRequests) sort.Strings(setRequests)
a := strings.Join(setRequests, ", ") + " request for container " + container.Name a := strings.Join(setRequests, ", ") + fmt.Sprintf(" request for %s %s", annotationPrefix, container.Name)
annotations = append(annotations, a) annotations = append(annotations, a)
} }
if len(setLimits) > 0 { if len(setLimits) > 0 {
sort.Strings(setLimits) sort.Strings(setLimits)
a := strings.Join(setLimits, ", ") + " limit for container " + container.Name a := strings.Join(setLimits, ", ") + fmt.Sprintf(" limit for %s %s", annotationPrefix, container.Name)
annotations = append(annotations, a) annotations = append(annotations, a)
} }
return annotations
}
// mergePodResourceRequirements merges enumerated requirements with default requirements
// it annotates the pod with information about what requirements were modified
func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) {
annotations := []string{}
for i := range pod.Spec.Containers {
annotations = mergeContainerResources(&pod.Spec.Containers[i], defaultRequirements, "container", annotations)
}
for i := range pod.Spec.InitContainers {
annotations = mergeContainerResources(&pod.Spec.InitContainers[i], defaultRequirements, "init container", annotations)
} }
if len(annotations) > 0 { if len(annotations) > 0 {
@ -441,7 +450,7 @@ func PodLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
} }
} }
// enforce pod limits // enforce pod limits on init containers
if limitType == api.LimitTypePod { if limitType == api.LimitTypePod {
containerRequests, containerLimits := []api.ResourceList{}, []api.ResourceList{} containerRequests, containerLimits := []api.ResourceList{}, []api.ResourceList{}
for j := range pod.Spec.Containers { for j := range pod.Spec.Containers {
@ -451,6 +460,28 @@ func PodLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
} }
podRequests := sum(containerRequests) podRequests := sum(containerRequests)
podLimits := sum(containerLimits) podLimits := sum(containerLimits)
for j := range pod.Spec.InitContainers {
container := &pod.Spec.InitContainers[j]
// take max(sum_containers, any_init_container)
for k, v := range container.Resources.Requests {
if v2, ok := podRequests[k]; ok {
if v.Cmp(v2) > 0 {
podRequests[k] = v
}
} else {
podRequests[k] = v
}
}
for k, v := range container.Resources.Limits {
if v2, ok := podLimits[k]; ok {
if v.Cmp(v2) > 0 {
podLimits[k] = v
}
} else {
podLimits[k] = v
}
}
}
for k, v := range limit.Min { for k, v := range limit.Min {
if err := minConstraint(limitType, k, v, podRequests, podLimits); err != nil { if err := minConstraint(limitType, k, v, podRequests, podLimits); err != nil {
errs = append(errs, err) errs = append(errs, err)

View File

@ -134,6 +134,17 @@ func validPod(name string, numContainers int, resources api.ResourceRequirements
return pod return pod
} }
func validPodInit(pod api.Pod, resources ...api.ResourceRequirements) api.Pod {
for i := 0; i < len(resources); i++ {
pod.Spec.InitContainers = append(pod.Spec.InitContainers, api.Container{
Image: "foo:V" + strconv.Itoa(i),
Resources: resources[i],
Name: "foo-" + strconv.Itoa(i),
})
}
return pod
}
func TestDefaultContainerResourceRequirements(t *testing.T) { func TestDefaultContainerResourceRequirements(t *testing.T) {
limitRange := validLimitRange() limitRange := validLimitRange()
expected := api.ResourceRequirements{ expected := api.ResourceRequirements{
@ -183,7 +194,7 @@ func TestMergePodResourceRequirements(t *testing.T) {
// pod with some resources enumerated should only merge empty // pod with some resources enumerated should only merge empty
input := getResourceRequirements(getResourceList("", "512Mi"), getResourceList("", "")) input := getResourceRequirements(getResourceList("", "512Mi"), getResourceList("", ""))
pod = validPod("limit-memory", 1, input) pod = validPodInit(validPod("limit-memory", 1, input), input)
expected = api.ResourceRequirements{ expected = api.ResourceRequirements{
Requests: api.ResourceList{ Requests: api.ResourceList{
api.ResourceCPU: defaultRequirements.Requests[api.ResourceCPU], api.ResourceCPU: defaultRequirements.Requests[api.ResourceCPU],
@ -198,11 +209,18 @@ func TestMergePodResourceRequirements(t *testing.T) {
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual) t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
} }
} }
for i := range pod.Spec.InitContainers {
actual := pod.Spec.InitContainers[i].Resources
if !api.Semantic.DeepEqual(expected, actual) {
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
}
}
verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu request for container foo-0; cpu, memory limit for container foo-0") verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu request for container foo-0; cpu, memory limit for container foo-0")
// pod with all resources enumerated should not merge anything // pod with all resources enumerated should not merge anything
input = getResourceRequirements(getResourceList("100m", "512Mi"), getResourceList("200m", "1G")) input = getResourceRequirements(getResourceList("100m", "512Mi"), getResourceList("200m", "1G"))
pod = validPod("limit-memory", 1, input) initInputs := []api.ResourceRequirements{getResourceRequirements(getResourceList("200m", "1G"), getResourceList("400m", "2G"))}
pod = validPodInit(validPod("limit-memory", 1, input), initInputs...)
expected = input expected = input
mergePodResourceRequirements(&pod, &defaultRequirements) mergePodResourceRequirements(&pod, &defaultRequirements)
for i := range pod.Spec.Containers { for i := range pod.Spec.Containers {
@ -211,6 +229,12 @@ func TestMergePodResourceRequirements(t *testing.T) {
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual) t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
} }
} }
for i := range pod.Spec.InitContainers {
actual := pod.Spec.InitContainers[i].Resources
if !api.Semantic.DeepEqual(initInputs[i], actual) {
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, initInputs[i], actual)
}
}
expectNoAnnotation(t, &pod) expectNoAnnotation(t, &pod)
} }
@ -273,6 +297,20 @@ func TestPodLimitFunc(t *testing.T) {
pod: validPod("pod-min-memory-request-limit", 2, getResourceRequirements(getResourceList("", "60Mi"), getResourceList("", "100Mi"))), pod: validPod("pod-min-memory-request-limit", 2, getResourceRequirements(getResourceList("", "60Mi"), getResourceList("", "100Mi"))),
limitRange: createLimitRange(api.LimitTypePod, getResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}), limitRange: createLimitRange(api.LimitTypePod, getResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
}, },
{
pod: validPodInit(
validPod("pod-init-min-memory-request", 2, getResourceRequirements(getResourceList("", "60Mi"), getResourceList("", ""))),
getResourceRequirements(getResourceList("", "100Mi"), getResourceList("", "")),
),
limitRange: createLimitRange(api.LimitTypePod, getResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-min-memory-request-limit", 2, getResourceRequirements(getResourceList("", "60Mi"), getResourceList("", "100Mi"))),
getResourceRequirements(getResourceList("", "80Mi"), getResourceList("", "100Mi")),
),
limitRange: createLimitRange(api.LimitTypePod, getResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{ {
pod: validPod("pod-max-cpu-request-limit", 2, getResourceRequirements(getResourceList("500m", ""), getResourceList("1", ""))), pod: validPod("pod-max-cpu-request-limit", 2, getResourceRequirements(getResourceList("500m", ""), getResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}), limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
@ -281,6 +319,22 @@ func TestPodLimitFunc(t *testing.T) {
pod: validPod("pod-max-cpu-limit", 2, getResourceRequirements(getResourceList("", ""), getResourceList("1", ""))), pod: validPod("pod-max-cpu-limit", 2, getResourceRequirements(getResourceList("", ""), getResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}), limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
}, },
{
pod: validPodInit(
validPod("pod-init-max-cpu-request-limit", 2, getResourceRequirements(getResourceList("500m", ""), getResourceList("1", ""))),
getResourceRequirements(getResourceList("1", ""), getResourceList("2", "")),
getResourceRequirements(getResourceList("1", ""), getResourceList("1", "")),
),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-max-cpu-limit", 2, getResourceRequirements(getResourceList("", ""), getResourceList("1", ""))),
getResourceRequirements(getResourceList("", ""), getResourceList("2", "")),
getResourceRequirements(getResourceList("", ""), getResourceList("2", "")),
),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{ {
pod: validPod("pod-max-mem-request-limit", 2, getResourceRequirements(getResourceList("", "250Mi"), getResourceList("", "500Mi"))), pod: validPod("pod-max-mem-request-limit", 2, getResourceRequirements(getResourceList("", "250Mi"), getResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}), limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
@ -387,6 +441,13 @@ func TestPodLimitFunc(t *testing.T) {
pod: validPod("pod-max-mem-limit", 3, getResourceRequirements(getResourceList("", ""), getResourceList("", "500Mi"))), pod: validPod("pod-max-mem-limit", 3, getResourceRequirements(getResourceList("", ""), getResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}), limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
}, },
{
pod: validPodInit(
validPod("pod-init-max-mem-limit", 1, getResourceRequirements(getResourceList("", ""), getResourceList("", "500Mi"))),
getResourceRequirements(getResourceList("", ""), getResourceList("", "1.5Gi")),
),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{ {
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getResourceList("", "250Mi"), getResourceList("", "500Mi"))), pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getResourceList("", "250Mi"), getResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getResourceList("", "1.5")), limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getResourceList("", "1.5")),
@ -403,7 +464,7 @@ func TestPodLimitFunc(t *testing.T) {
func TestPodLimitFuncApplyDefault(t *testing.T) { func TestPodLimitFuncApplyDefault(t *testing.T) {
limitRange := validLimitRange() limitRange := validLimitRange()
testPod := validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})) testPod := validPodInit(validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})), getResourceRequirements(api.ResourceList{}, api.ResourceList{}))
err := PodLimitFunc(&limitRange, &testPod) err := PodLimitFunc(&limitRange, &testPod)
if err != nil { if err != nil {
t.Errorf("Unexpected error for valid pod: %v, %v", testPod.Name, err) t.Errorf("Unexpected error for valid pod: %v, %v", testPod.Name, err)
@ -429,6 +490,27 @@ func TestPodLimitFuncApplyDefault(t *testing.T) {
t.Errorf("Unexpected cpu value %s", requestCpu) t.Errorf("Unexpected cpu value %s", requestCpu)
} }
} }
for i := range testPod.Spec.InitContainers {
container := testPod.Spec.InitContainers[i]
limitMemory := container.Resources.Limits.Memory().String()
limitCpu := container.Resources.Limits.Cpu().String()
requestMemory := container.Resources.Requests.Memory().String()
requestCpu := container.Resources.Requests.Cpu().String()
if limitMemory != "10Mi" {
t.Errorf("Unexpected memory value %s", limitMemory)
}
if limitCpu != "75m" {
t.Errorf("Unexpected cpu value %s", limitCpu)
}
if requestMemory != "5Mi" {
t.Errorf("Unexpected memory value %s", requestMemory)
}
if requestCpu != "50m" {
t.Errorf("Unexpected cpu value %s", requestCpu)
}
}
} }
func TestLimitRangerIgnoresSubresource(t *testing.T) { func TestLimitRangerIgnoresSubresource(t *testing.T) {