Commit 31a1145a authored by derekwaynecarr's avatar derekwaynecarr

Implement LimitRanger plugin

parent 091cbe5f
{
"id": "limits",
"kind": "LimitRange",
"apiVersion": "v1beta1",
"spec": {
"limits": [
{
"kind": "pods",
"max": {
"memory": "1073741824",
"cpu": "2",
},
"min": {
"memory": "1048576",
"cpu": "0.25"
}
},
{
"kind": "containers",
"max": {
"memory": "1073741824",
"cpu": "2",
},
"min": {
"memory": "1048576",
"cpu": "0.25"
}
},
],
}
}
/*
Copyright 2014 Google Inc. 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 limitranger
import (
"fmt"
"io"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
func init() {
admission.RegisterPlugin("LimitRanger", func(client client.Interface, config io.Reader) (admission.Interface, error) {
return NewLimitRanger(client, PodLimitFunc), nil
})
}
// limitRanger enforces usage limits on a per resource basis in the namespace
type limitRanger struct {
client client.Interface
limitFunc LimitFunc
}
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
func (l *limitRanger) Admit(a admission.Attributes) (err error) {
// ignore deletes
if a.GetOperation() == "DELETE" {
return nil
}
// look for a limit range in current namespace that requires enforcement
items, err := l.client.LimitRanges(a.GetNamespace()).List(labels.Everything())
if err != nil {
return err
}
// ensure it meets each prescribed min/max
for i := range items.Items {
limitRange := &items.Items[i]
err = l.limitFunc(limitRange, a.GetKind(), a.GetObject())
if err != nil {
return err
}
}
return nil
}
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
func NewLimitRanger(client client.Interface, limitFunc LimitFunc) admission.Interface {
return &limitRanger{client: client, limitFunc: limitFunc}
}
func Min(a int64, b int64) int64 {
if a < b {
return a
}
return b
}
func Max(a int64, b int64) int64 {
if a > b {
return a
}
return b
}
// PodLimitFunc enforces that a pod spec does not exceed any limits specified on the supplied limit range
func PodLimitFunc(limitRange *api.LimitRange, kind string, obj runtime.Object) error {
if kind != "pods" {
return nil
}
pod := obj.(*api.Pod)
podCPU := int64(0)
podMem := int64(0)
minContainerCPU := int64(0)
minContainerMem := int64(0)
maxContainerCPU := int64(0)
maxContainerMem := int64(0)
for i := range pod.Spec.Containers {
container := pod.Spec.Containers[i]
containerCPU := container.CPU.MilliValue()
containerMem := container.Memory.Value()
if i == 0 {
minContainerCPU = containerCPU
minContainerMem = containerMem
maxContainerCPU = containerCPU
maxContainerMem = containerMem
}
podCPU = podCPU + container.CPU.MilliValue()
podMem = podMem + container.Memory.Value()
minContainerCPU = Min(containerCPU, minContainerCPU)
minContainerMem = Min(containerMem, minContainerMem)
maxContainerCPU = Max(containerCPU, maxContainerCPU)
maxContainerMem = Max(containerMem, maxContainerMem)
}
for i := range limitRange.Spec.Limits {
limit := limitRange.Spec.Limits[i]
// enforce max
for k, v := range limit.Max {
observed := int64(0)
enforced := int64(0)
var err error
switch k {
case api.ResourceMemory:
enforced = v.Value()
switch limit.Kind {
case "pods":
observed = podMem
err = fmt.Errorf("Maximum memory usage per pod is %s", v.String())
case "containers":
observed = maxContainerMem
err = fmt.Errorf("Maximum memory usage per container is %s", v.String())
}
case api.ResourceCPU:
enforced = v.MilliValue()
switch limit.Kind {
case "pods":
observed = podCPU
err = fmt.Errorf("Maximum CPU usage per pod is %s, but requested %s", v.String(), resource.NewMilliQuantity(observed, resource.DecimalSI))
case "containers":
observed = maxContainerCPU
err = fmt.Errorf("Maximum CPU usage per container is %s", v.String())
}
}
if observed > enforced {
return apierrors.NewForbidden(kind, pod.Name, err)
}
}
for k, v := range limit.Min {
observed := int64(0)
enforced := int64(0)
var err error
switch k {
case api.ResourceMemory:
enforced = v.Value()
switch limit.Kind {
case "pods":
observed = podMem
err = fmt.Errorf("Minimum memory usage per pod is %s", v.String())
case "containers":
observed = maxContainerMem
err = fmt.Errorf("Minimum memory usage per container is %s", v.String())
}
case api.ResourceCPU:
enforced = v.MilliValue()
switch limit.Kind {
case "pods":
observed = podCPU
err = fmt.Errorf("Minimum CPU usage per pod is %s", v.String())
case "containers":
observed = maxContainerCPU
err = fmt.Errorf("Minimum CPU usage per container is %s", v.String())
}
}
if observed < enforced {
return apierrors.NewForbidden(kind, pod.Name, err)
}
}
}
return nil
}
/*
Copyright 2014 Google Inc. 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 limitranger
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
)
func TestPodLimitFunc(t *testing.T) {
limitRange := &api.LimitRange{
ObjectMeta: api.ObjectMeta{
Name: "abc",
},
Spec: api.LimitRangeSpec{
Limits: []api.LimitRangeItem{
{
Kind: "pods",
Max: api.ResourceList{
api.ResourceCPU: resource.MustParse("200m"),
api.ResourceMemory: resource.MustParse("4Gi"),
},
Min: api.ResourceList{
api.ResourceCPU: resource.MustParse("50m"),
api.ResourceMemory: resource.MustParse("2Mi"),
},
},
{
Kind: "containers",
Max: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("2Gi"),
},
Min: api.ResourceList{
api.ResourceCPU: resource.MustParse("25m"),
api.ResourceMemory: resource.MustParse("1Mi"),
},
},
},
},
}
successCases := []api.Pod{
{
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "foo:V1",
CPU: resource.MustParse("100m"),
Memory: resource.MustParse("2Gi"),
},
{
Image: "boo:V1",
CPU: resource.MustParse("100m"),
Memory: resource.MustParse("2Gi"),
},
},
},
},
{
ObjectMeta: api.ObjectMeta{Name: "bar"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("100m"),
Memory: resource.MustParse("2Gi"),
},
},
},
},
}
errorCases := map[string]api.Pod{
"min-container-cpu": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("25m"),
Memory: resource.MustParse("2Gi"),
},
},
},
},
"max-container-cpu": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("110m"),
Memory: resource.MustParse("1Gi"),
},
},
},
},
"min-container-mem": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("30m"),
Memory: resource.MustParse("0"),
},
},
},
},
"max-container-mem": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("30m"),
Memory: resource.MustParse("3Gi"),
},
},
},
},
"min-pod-cpu": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("40m"),
Memory: resource.MustParse("2Gi"),
},
},
},
},
"max-pod-cpu": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("1Mi"),
},
{
Image: "boo:V2",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("1Mi"),
},
{
Image: "boo:V3",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("1Mi"),
},
{
Image: "boo:V4",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("1Mi"),
},
},
},
},
"max-pod-memory": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("2Gi"),
},
{
Image: "boo:V2",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("2Gi"),
},
{
Image: "boo:V3",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("2Gi"),
},
},
},
},
"min-pod-memory": {
ObjectMeta: api.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{
Containers: []api.Container{
{
Image: "boo:V1",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("0"),
},
{
Image: "boo:V2",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("0"),
},
{
Image: "boo:V3",
CPU: resource.MustParse("60m"),
Memory: resource.MustParse("0"),
},
},
},
},
}
for i := range successCases {
err := PodLimitFunc(limitRange, "pods", &successCases[i])
if err != nil {
t.Errorf("Unexpected error for valid pod: %v, %v", successCases[i].Name, err)
}
}
for k, v := range errorCases {
err := PodLimitFunc(limitRange, "pods", &v)
if err == nil {
t.Errorf("Expected error for %s", k)
}
}
}
/*
Copyright 2014 Google Inc. 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 limitranger
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
// LimitFunc is a pluggable function to enforce limits on the object
type LimitFunc func(limitRange *api.LimitRange, kind string, obj runtime.Object) error
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment