Switch to github.com/golang/dep for vendoring

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2017-01-31 16:45:59 -08:00
parent d6ab91be27
commit 8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions

View file

@ -0,0 +1,65 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"admission.go",
"interfaces.go",
],
tags = ["automanaged"],
deps = [
"//pkg/admission:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/controller/informers:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//vendor:github.com/hashicorp/golang-lru",
"//vendor:k8s.io/apimachinery/pkg/api/meta",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
],
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/admission:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/testing/core:go_default_library",
"//pkg/controller/informers:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,554 @@
/*
Copyright 2014 The Kubernetes Authors.
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"
"sort"
"strings"
"time"
lru "github.com/hashicorp/golang-lru"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
coreinternallisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
"k8s.io/kubernetes/pkg/controller/informers"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
)
const (
limitRangerAnnotation = "kubernetes.io/limit-ranger"
)
func init() {
admission.RegisterPlugin("LimitRanger", func(config io.Reader) (admission.Interface, error) {
return NewLimitRanger(&DefaultLimitRangerActions{})
})
}
// limitRanger enforces usage limits on a per resource basis in the namespace
type limitRanger struct {
*admission.Handler
client internalclientset.Interface
actions LimitRangerActions
lister coreinternallisters.LimitRangeLister
// liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
// This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
// We track the lookup result here so that for repeated requests, we don't look it up very often.
liveLookupCache *lru.Cache
liveTTL time.Duration
}
type liveLookupEntry struct {
expiry time.Time
items []*api.LimitRange
}
func (l *limitRanger) SetInformerFactory(f informers.SharedInformerFactory) {
limitRangeInformer := f.InternalLimitRanges().Informer()
l.SetReadyFunc(limitRangeInformer.HasSynced)
l.lister = f.InternalLimitRanges().Lister()
}
func (l *limitRanger) Validate() error {
if l.lister == nil {
return fmt.Errorf("missing limitRange lister")
}
if l.client == nil {
return fmt.Errorf("missing client")
}
return nil
}
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
func (l *limitRanger) Admit(a admission.Attributes) (err error) {
if !l.actions.SupportsAttributes(a) {
return nil
}
obj := a.GetObject()
name := "Unknown"
if obj != nil {
name, _ = meta.NewAccessor().Name(obj)
if len(name) == 0 {
name, _ = meta.NewAccessor().GenerateName(obj)
}
}
items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything())
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource()))
}
// if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it.
if len(items) == 0 {
lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace())
if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
// TODO: If there are multiple operations at the same time and cache has just expired,
// this may cause multiple List operations being issued at the same time.
// If there is already in-flight List() for a given namespace, we should wait until
// it is finished and cache is updated instead of doing the same, also to avoid
// throttling - see #22422 for details.
liveList, err := l.client.Core().LimitRanges(a.GetNamespace()).List(api.ListOptions{})
if err != nil {
return admission.NewForbidden(a, err)
}
newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)}
for i := range liveList.Items {
newEntry.items = append(newEntry.items, &liveList.Items[i])
}
l.liveLookupCache.Add(a.GetNamespace(), newEntry)
lruItemObj = newEntry
}
lruEntry := lruItemObj.(liveLookupEntry)
for i := range lruEntry.items {
items = append(items, lruEntry.items[i])
}
}
// ensure it meets each prescribed min/max
for i := range items {
limitRange := items[i]
if !l.actions.SupportsLimit(limitRange) {
continue
}
err = l.actions.Limit(limitRange, a.GetResource().Resource, a.GetObject())
if err != nil {
return admission.NewForbidden(a, err)
}
}
return nil
}
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
func NewLimitRanger(actions LimitRangerActions) (admission.Interface, error) {
liveLookupCache, err := lru.New(10000)
if err != nil {
return nil, err
}
if actions == nil {
actions = &DefaultLimitRangerActions{}
}
return &limitRanger{
Handler: admission.NewHandler(admission.Create, admission.Update),
actions: actions,
liveLookupCache: liveLookupCache,
liveTTL: time.Duration(30 * time.Second),
}, nil
}
var _ = kubeapiserveradmission.WantsInternalClientSet(&limitRanger{})
func (a *limitRanger) SetInternalClientSet(client internalclientset.Interface) {
a.client = client
}
// defaultContainerResourceRequirements returns the default requirements for a container
// the requirement.Limits are taken from the LimitRange defaults (if specified)
// the requirement.Requests are taken from the LimitRange default request (if specified)
func defaultContainerResourceRequirements(limitRange *api.LimitRange) api.ResourceRequirements {
requirements := api.ResourceRequirements{}
requirements.Requests = api.ResourceList{}
requirements.Limits = api.ResourceList{}
for i := range limitRange.Spec.Limits {
limit := limitRange.Spec.Limits[i]
if limit.Type == api.LimitTypeContainer {
for k, v := range limit.DefaultRequest {
value := v.Copy()
requirements.Requests[k] = *value
}
for k, v := range limit.Default {
value := v.Copy()
requirements.Limits[k] = *value
}
}
}
return requirements
}
// mergeContainerResources handles defaulting all of the resources on a container.
func mergeContainerResources(container *api.Container, defaultRequirements *api.ResourceRequirements, annotationPrefix string, annotations []string) []string {
setRequests := []string{}
setLimits := []string{}
if container.Resources.Limits == nil {
container.Resources.Limits = api.ResourceList{}
}
if container.Resources.Requests == nil {
container.Resources.Requests = api.ResourceList{}
}
for k, v := range defaultRequirements.Limits {
_, found := container.Resources.Limits[k]
if !found {
container.Resources.Limits[k] = *v.Copy()
setLimits = append(setLimits, string(k))
}
}
for k, v := range defaultRequirements.Requests {
_, found := container.Resources.Requests[k]
if !found {
container.Resources.Requests[k] = *v.Copy()
setRequests = append(setRequests, string(k))
}
}
if len(setRequests) > 0 {
sort.Strings(setRequests)
a := strings.Join(setRequests, ", ") + fmt.Sprintf(" request for %s %s", annotationPrefix, container.Name)
annotations = append(annotations, a)
}
if len(setLimits) > 0 {
sort.Strings(setLimits)
a := strings.Join(setLimits, ", ") + fmt.Sprintf(" limit for %s %s", annotationPrefix, container.Name)
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 pod.ObjectMeta.Annotations == nil {
pod.ObjectMeta.Annotations = make(map[string]string)
}
val := "LimitRanger plugin set: " + strings.Join(annotations, "; ")
pod.ObjectMeta.Annotations[limitRangerAnnotation] = val
}
}
// requestLimitEnforcedValues returns the specified values at a common precision to support comparability
func requestLimitEnforcedValues(requestQuantity, limitQuantity, enforcedQuantity resource.Quantity) (request, limit, enforced int64) {
request = requestQuantity.Value()
limit = limitQuantity.Value()
enforced = enforcedQuantity.Value()
// do a more precise comparison if possible (if the value won't overflow)
if request <= resource.MaxMilliValue && limit <= resource.MaxMilliValue && enforced <= resource.MaxMilliValue {
request = requestQuantity.MilliValue()
limit = limitQuantity.MilliValue()
enforced = enforcedQuantity.MilliValue()
}
return
}
// minConstraint enforces the min constraint over the specified resource
func minConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
req, reqExists := request[resourceName]
lim, limExists := limit[resourceName]
observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
if !reqExists {
return fmt.Errorf("minimum %s usage per %s is %s. No request is specified.", resourceName, limitType, enforced.String())
}
if observedReqValue < enforcedValue {
return fmt.Errorf("minimum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
}
if limExists && (observedLimValue < enforcedValue) {
return fmt.Errorf("minimum %s usage per %s is %s, but limit is %s.", resourceName, limitType, enforced.String(), lim.String())
}
return nil
}
// maxRequestConstraint enforces the max constraint over the specified resource
// use when specify LimitType resource doesn't recognize limit values
func maxRequestConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList) error {
req, reqExists := request[resourceName]
observedReqValue, _, enforcedValue := requestLimitEnforcedValues(req, resource.Quantity{}, enforced)
if !reqExists {
return fmt.Errorf("maximum %s usage per %s is %s. No request is specified.", resourceName, limitType, enforced.String())
}
if observedReqValue > enforcedValue {
return fmt.Errorf("maximum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
}
return nil
}
// maxConstraint enforces the max constraint over the specified resource
func maxConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
req, reqExists := request[resourceName]
lim, limExists := limit[resourceName]
observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
if !limExists {
return fmt.Errorf("maximum %s usage per %s is %s. No limit is specified.", resourceName, limitType, enforced.String())
}
if observedLimValue > enforcedValue {
return fmt.Errorf("maximum %s usage per %s is %s, but limit is %s.", resourceName, limitType, enforced.String(), lim.String())
}
if reqExists && (observedReqValue > enforcedValue) {
return fmt.Errorf("maximum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
}
return nil
}
// limitRequestRatioConstraint enforces the limit to request ratio over the specified resource
func limitRequestRatioConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
req, reqExists := request[resourceName]
lim, limExists := limit[resourceName]
observedReqValue, observedLimValue, _ := requestLimitEnforcedValues(req, lim, enforced)
if !reqExists || (observedReqValue == int64(0)) {
return fmt.Errorf("%s max limit to request ratio per %s is %s, but no request is specified or request is 0.", resourceName, limitType, enforced.String())
}
if !limExists || (observedLimValue == int64(0)) {
return fmt.Errorf("%s max limit to request ratio per %s is %s, but no limit is specified or limit is 0.", resourceName, limitType, enforced.String())
}
observedRatio := float64(observedLimValue) / float64(observedReqValue)
displayObservedRatio := observedRatio
maxLimitRequestRatio := float64(enforced.Value())
if enforced.Value() <= resource.MaxMilliValue {
observedRatio = observedRatio * 1000
maxLimitRequestRatio = float64(enforced.MilliValue())
}
if observedRatio > maxLimitRequestRatio {
return fmt.Errorf("%s max limit to request ratio per %s is %s, but provided ratio is %f.", resourceName, limitType, enforced.String(), displayObservedRatio)
}
return nil
}
// sum takes the total of each named resource across all inputs
// if a key is not in each input, then the output resource list will omit the key
func sum(inputs []api.ResourceList) api.ResourceList {
result := api.ResourceList{}
keys := []api.ResourceName{}
for i := range inputs {
for k := range inputs[i] {
keys = append(keys, k)
}
}
for _, key := range keys {
total, isSet := int64(0), true
for i := range inputs {
input := inputs[i]
v, exists := input[key]
if exists {
if key == api.ResourceCPU {
total = total + v.MilliValue()
} else {
total = total + v.Value()
}
} else {
isSet = false
}
}
if isSet {
if key == api.ResourceCPU {
result[key] = *(resource.NewMilliQuantity(total, resource.DecimalSI))
} else {
result[key] = *(resource.NewQuantity(total, resource.DecimalSI))
}
}
}
return result
}
// DefaultLimitRangerActions is the default implementation of LimitRangerActions.
type DefaultLimitRangerActions struct{}
// ensure DefaultLimitRangerActions implements the LimitRangerActions interface.
var _ LimitRangerActions = &DefaultLimitRangerActions{}
// Limit enforces resource requirements of incoming resources against enumerated constraints
// on the LimitRange. It may modify the incoming object to apply default resource requirements
// if not specified, and enumerated on the LimitRange
func (d *DefaultLimitRangerActions) Limit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
switch resourceName {
case "pods":
return PodLimitFunc(limitRange, obj.(*api.Pod))
case "persistentvolumeclaims":
return PersistentVolumeClaimLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim))
}
return nil
}
// SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs).
// Also ignores any call that has a subresource defined.
func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool {
if a.GetSubresource() != "" {
return false
}
return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim")
}
// SupportsLimit always returns true.
func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *api.LimitRange) bool {
return true
}
// PersistentVolumeClaimLimitFunc enforces storage limits for PVCs.
// Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange.
// Claims will not be modified with default values because storage is a required part of pvc.Spec.
// All storage enforced values *only* apply to pvc.Spec.Resources.Requests.
func PersistentVolumeClaimLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error {
var errs []error
for i := range limitRange.Spec.Limits {
limit := limitRange.Spec.Limits[i]
limitType := limit.Type
if limitType == api.LimitTypePersistentVolumeClaim {
for k, v := range limit.Min {
// normal usage of minConstraint. pvc.Spec.Resources.Limits is not recognized as user input
if err := minConstraint(limitType, k, v, pvc.Spec.Resources.Requests, api.ResourceList{}); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.Max {
// We want to enforce the max of the LimitRange against what
// the user requested.
if err := maxRequestConstraint(limitType, k, v, pvc.Spec.Resources.Requests); err != nil {
errs = append(errs, err)
}
}
}
}
return utilerrors.NewAggregate(errs)
}
// PodLimitFunc enforces resource requirements enumerated by the pod against
// the specified LimitRange. The pod may be modified to apply default resource
// requirements if not specified, and enumerated on the LimitRange
func PodLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
var errs []error
defaultResources := defaultContainerResourceRequirements(limitRange)
mergePodResourceRequirements(pod, &defaultResources)
for i := range limitRange.Spec.Limits {
limit := limitRange.Spec.Limits[i]
limitType := limit.Type
// enforce container limits
if limitType == api.LimitTypeContainer {
for j := range pod.Spec.Containers {
container := &pod.Spec.Containers[j]
for k, v := range limit.Min {
if err := minConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.Max {
if err := maxConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.MaxLimitRequestRatio {
if err := limitRequestRatioConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
errs = append(errs, err)
}
}
}
for j := range pod.Spec.InitContainers {
container := &pod.Spec.InitContainers[j]
for k, v := range limit.Min {
if err := minConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.Max {
if err := maxConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.MaxLimitRequestRatio {
if err := limitRequestRatioConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
errs = append(errs, err)
}
}
}
}
// enforce pod limits on init containers
if limitType == api.LimitTypePod {
containerRequests, containerLimits := []api.ResourceList{}, []api.ResourceList{}
for j := range pod.Spec.Containers {
container := &pod.Spec.Containers[j]
containerRequests = append(containerRequests, container.Resources.Requests)
containerLimits = append(containerLimits, container.Resources.Limits)
}
podRequests := sum(containerRequests)
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 {
if err := minConstraint(limitType, k, v, podRequests, podLimits); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.Max {
if err := maxConstraint(limitType, k, v, podRequests, podLimits); err != nil {
errs = append(errs, err)
}
}
for k, v := range limit.MaxLimitRequestRatio {
if err := limitRequestRatioConstraint(limitType, k, v, podRequests, podLimits); err != nil {
errs = append(errs, err)
}
}
}
}
return utilerrors.NewAggregate(errs)
}

View file

@ -0,0 +1,662 @@
/*
Copyright 2014 The Kubernetes Authors.
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"
"strconv"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/controller/informers"
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
)
func getComputeResourceList(cpu, memory string) api.ResourceList {
res := api.ResourceList{}
if cpu != "" {
res[api.ResourceCPU] = resource.MustParse(cpu)
}
if memory != "" {
res[api.ResourceMemory] = resource.MustParse(memory)
}
return res
}
func getStorageResourceList(storage string) api.ResourceList {
res := api.ResourceList{}
if storage != "" {
res[api.ResourceStorage] = resource.MustParse(storage)
}
return res
}
func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
res := api.ResourceRequirements{}
res.Requests = requests
res.Limits = limits
return res
}
// createLimitRange creates a limit range with the specified data
func createLimitRange(limitType api.LimitType, min, max, defaultLimit, defaultRequest, maxLimitRequestRatio api.ResourceList) api.LimitRange {
return api.LimitRange{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "test",
},
Spec: api.LimitRangeSpec{
Limits: []api.LimitRangeItem{
{
Type: limitType,
Min: min,
Max: max,
Default: defaultLimit,
DefaultRequest: defaultRequest,
MaxLimitRequestRatio: maxLimitRequestRatio,
},
},
},
}
}
func validLimitRange() api.LimitRange {
return api.LimitRange{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "test",
},
Spec: api.LimitRangeSpec{
Limits: []api.LimitRangeItem{
{
Type: api.LimitTypePod,
Max: getComputeResourceList("200m", "4Gi"),
Min: getComputeResourceList("50m", "2Mi"),
},
{
Type: api.LimitTypeContainer,
Max: getComputeResourceList("100m", "2Gi"),
Min: getComputeResourceList("25m", "1Mi"),
Default: getComputeResourceList("75m", "10Mi"),
DefaultRequest: getComputeResourceList("50m", "5Mi"),
},
},
},
}
}
func validLimitRangeNoDefaults() api.LimitRange {
return api.LimitRange{
ObjectMeta: api.ObjectMeta{
Name: "abc",
Namespace: "test",
},
Spec: api.LimitRangeSpec{
Limits: []api.LimitRangeItem{
{
Type: api.LimitTypePod,
Max: getComputeResourceList("200m", "4Gi"),
Min: getComputeResourceList("50m", "2Mi"),
},
{
Type: api.LimitTypeContainer,
Max: getComputeResourceList("100m", "2Gi"),
Min: getComputeResourceList("25m", "1Mi"),
},
},
},
}
}
func validPod(name string, numContainers int, resources api.ResourceRequirements) api.Pod {
pod := api.Pod{
ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test"},
Spec: api.PodSpec{},
}
pod.Spec.Containers = make([]api.Container, 0, numContainers)
for i := 0; i < numContainers; i++ {
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
Image: "foo:V" + strconv.Itoa(i),
Resources: resources,
Name: "foo-" + strconv.Itoa(i),
})
}
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) {
limitRange := validLimitRange()
expected := api.ResourceRequirements{
Requests: getComputeResourceList("50m", "5Mi"),
Limits: getComputeResourceList("75m", "10Mi"),
}
actual := defaultContainerResourceRequirements(&limitRange)
if !api.Semantic.DeepEqual(expected, actual) {
t.Errorf("actual.Limits != expected.Limits; %v != %v", actual.Limits, expected.Limits)
t.Errorf("actual.Requests != expected.Requests; %v != %v", actual.Requests, expected.Requests)
t.Errorf("expected != actual; %v != %v", expected, actual)
}
}
func verifyAnnotation(t *testing.T, pod *api.Pod, expected string) {
a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]
if !ok {
t.Errorf("No annotation but expected %v", expected)
}
if a != expected {
t.Errorf("Wrong annotation set by Limit Ranger: got %v, expected %v", a, expected)
}
}
func expectNoAnnotation(t *testing.T, pod *api.Pod) {
if a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]; ok {
t.Errorf("Expected no annotation but got %v", a)
}
}
func TestMergePodResourceRequirements(t *testing.T) {
limitRange := validLimitRange()
// pod with no resources enumerated should get each resource from default request
expected := getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))
pod := validPod("empty-resources", 1, expected)
defaultRequirements := defaultContainerResourceRequirements(&limitRange)
mergePodResourceRequirements(&pod, &defaultRequirements)
for i := range pod.Spec.Containers {
actual := pod.Spec.Containers[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, memory request for container foo-0; cpu, memory limit for container foo-0")
// pod with some resources enumerated should only merge empty
input := getResourceRequirements(getComputeResourceList("", "512Mi"), getComputeResourceList("", ""))
pod = validPodInit(validPod("limit-memory", 1, input), input)
expected = api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceCPU: defaultRequirements.Requests[api.ResourceCPU],
api.ResourceMemory: resource.MustParse("512Mi"),
},
Limits: defaultRequirements.Limits,
}
mergePodResourceRequirements(&pod, &defaultRequirements)
for i := range pod.Spec.Containers {
actual := pod.Spec.Containers[i].Resources
if !api.Semantic.DeepEqual(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")
// pod with all resources enumerated should not merge anything
input = getResourceRequirements(getComputeResourceList("100m", "512Mi"), getComputeResourceList("200m", "1G"))
initInputs := []api.ResourceRequirements{getResourceRequirements(getComputeResourceList("200m", "1G"), getComputeResourceList("400m", "2G"))}
pod = validPodInit(validPod("limit-memory", 1, input), initInputs...)
expected = input
mergePodResourceRequirements(&pod, &defaultRequirements)
for i := range pod.Spec.Containers {
actual := pod.Spec.Containers[i].Resources
if !api.Semantic.DeepEqual(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)
}
func TestPodLimitFunc(t *testing.T) {
type testCase struct {
pod api.Pod
limitRange api.LimitRange
}
successCases := []testCase{
{
pod: validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("200m", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("750m", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1.5", "")),
},
{
pod: validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-cpu-request", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
getResourceRequirements(getComputeResourceList("", "100Mi"), getComputeResourceList("", "")),
),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
getResourceRequirements(getComputeResourceList("", "80Mi"), getComputeResourceList("", "100Mi")),
),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("2", "")),
getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("1", "")),
),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-mem-request-limit", 2, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-mem-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "300Mi"), getComputeResourceList("", "450Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
},
}
for i := range successCases {
test := successCases[i]
err := PodLimitFunc(&test.limitRange, &test.pod)
if err != nil {
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
}
}
errorCases := []testCase{
{
pod: validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("200m", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", "100Mi"))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-min-memory-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("2500m", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2500m", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("1250m", ""), getComputeResourceList("2500m", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1", "")),
},
{
pod: validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "2Gi"))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "2Gi"))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("ctr-max-mem-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-cpu-request-limit", 3, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-cpu-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-mem-request-limit", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-mem-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPodInit(
validPod("pod-init-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "1.5Gi")),
),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
},
}
for i := range errorCases {
test := errorCases[i]
err := PodLimitFunc(&test.limitRange, &test.pod)
if err == nil {
t.Errorf("Expected error for pod: %s", test.pod.Name)
}
}
}
func TestPodLimitFuncApplyDefault(t *testing.T) {
limitRange := validLimitRange()
testPod := validPodInit(validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})), getResourceRequirements(api.ResourceList{}, api.ResourceList{}))
err := PodLimitFunc(&limitRange, &testPod)
if err != nil {
t.Errorf("Unexpected error for valid pod: %v, %v", testPod.Name, err)
}
for i := range testPod.Spec.Containers {
container := testPod.Spec.Containers[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)
}
}
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) {
limitRange := validLimitRangeNoDefaults()
mockClient := newMockClientForTest([]api.LimitRange{limitRange})
handler, informerFactory, err := newHandlerForTest(mockClient)
if err != nil {
t.Errorf("unexpected error initializing handler: %v", err)
}
informerFactory.Start(wait.NeverStop)
testPod := validPod("testPod", 1, api.ResourceRequirements{})
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if err == nil {
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
}
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
if err != nil {
t.Errorf("Should have ignored calls to any subresource of pod %v", err)
}
}
func TestLimitRangerAdmitPod(t *testing.T) {
limitRange := validLimitRangeNoDefaults()
mockClient := newMockClientForTest([]api.LimitRange{limitRange})
handler, informerFactory, err := newHandlerForTest(mockClient)
if err != nil {
t.Errorf("unexpected error initializing handler: %v", err)
}
informerFactory.Start(wait.NeverStop)
testPod := validPod("testPod", 1, api.ResourceRequirements{})
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
if err == nil {
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
}
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
if err != nil {
t.Errorf("Should have ignored calls to any subresource of pod %v", err)
}
}
// newMockClientForTest creates a mock client that returns a client configured for the specified list of limit ranges
func newMockClientForTest(limitRanges []api.LimitRange) *fake.Clientset {
mockClient := &fake.Clientset{}
mockClient.AddReactor("list", "limitranges", func(action core.Action) (bool, runtime.Object, error) {
limitRangeList := &api.LimitRangeList{
ListMeta: metav1.ListMeta{
ResourceVersion: fmt.Sprintf("%d", len(limitRanges)),
},
}
for index, value := range limitRanges {
value.ResourceVersion = fmt.Sprintf("%d", index)
limitRangeList.Items = append(limitRangeList.Items, value)
}
return true, limitRangeList, nil
})
return mockClient
}
// newHandlerForTest returns a handler configured for testing.
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
f := informers.NewSharedInformerFactory(nil, c, 5*time.Minute)
handler, err := NewLimitRanger(&DefaultLimitRangerActions{})
if err != nil {
return nil, f, err
}
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil)
pluginInitializer.Initialize(handler)
err = admission.Validate(handler)
return handler, f, err
}
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) api.PersistentVolumeClaim {
pvc := api.PersistentVolumeClaim{
ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test"},
Spec: api.PersistentVolumeClaimSpec{
Resources: resources,
},
}
return pvc
}
func TestPersistentVolumeClaimLimitFunc(t *testing.T) {
type testCase struct {
pvc api.PersistentVolumeClaim
limitRange api.LimitRange
}
successCases := []testCase{
{
pvc: validPersistentVolumeClaim("pvc-is-min-storage-request", getResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pvc: validPersistentVolumeClaim("pvc-is-max-storage-request", getResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, api.ResourceList{}, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pvc: validPersistentVolumeClaim("pvc-no-minmax-storage-request", getResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList(""), getStorageResourceList(""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pvc: validPersistentVolumeClaim("pvc-within-minmax-storage-request", getResourceRequirements(getStorageResourceList("5Gi"), getStorageResourceList(""))),
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("10Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
}
for i := range successCases {
test := successCases[i]
err := PersistentVolumeClaimLimitFunc(&test.limitRange, &test.pvc)
if err != nil {
t.Errorf("Unexpected error for pvc: %s, %v", test.pvc.Name, err)
}
}
errorCases := []testCase{
{
pvc: validPersistentVolumeClaim("pvc-below-min-storage-request", getResourceRequirements(getStorageResourceList("500Mi"), getStorageResourceList(""))),
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
{
pvc: validPersistentVolumeClaim("pvc-exceeds-max-storage-request", getResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
},
}
for i := range errorCases {
test := errorCases[i]
err := PersistentVolumeClaimLimitFunc(&test.limitRange, &test.pvc)
if err == nil {
t.Errorf("Expected error for pvc: %s", test.pvc.Name)
}
}
}

View file

@ -0,0 +1,34 @@
/*
Copyright 2014 The Kubernetes Authors.
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 (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
)
type LimitRangerActions interface {
// Limit is a pluggable function to enforce limits on the object.
Limit(limitRange *api.LimitRange, kind string, obj runtime.Object) error
// SupportsAttributes is a pluggable function to allow overridding what resources the limitranger
// supports.
SupportsAttributes(attr admission.Attributes) bool
// SupportsLimit is a pluggable function to allow ignoring limits that should not be applied
// for any reason.
SupportsLimit(limitRange *api.LimitRange) bool
}