diff --git a/plugin/pkg/admission/limitranger/admission.go b/plugin/pkg/admission/limitranger/admission.go index eaa3802385b..73336b7f56d 100644 --- a/plugin/pkg/admission/limitranger/admission.go +++ b/plugin/pkg/admission/limitranger/admission.go @@ -64,6 +64,10 @@ type LimitRanger struct { liveTTL time.Duration } +var _ admission.MutationInterface = &LimitRanger{} +var _ admission.ValidationInterface = &LimitRanger{} +var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &LimitRanger{} + type liveLookupEntry struct { expiry time.Time items []*api.LimitRange @@ -87,6 +91,15 @@ func (l *LimitRanger) ValidateInitialization() error { // Admit admits resources into cluster that do not violate any defined LimitRange in the namespace func (l *LimitRanger) Admit(a admission.Attributes) (err error) { + return l.runLimitFunc(a, l.actions.MutateLimit) +} + +// Validate admits resources into cluster that do not violate any defined LimitRange in the namespace +func (l *LimitRanger) Validate(a admission.Attributes) (err error) { + return l.runLimitFunc(a, l.actions.ValidateLimit) +} + +func (l *LimitRanger) runLimitFunc(a admission.Attributes, limitFn func(limitRange *api.LimitRange, kind string, obj runtime.Object) error) (err error) { if !l.actions.SupportsAttributes(a) { return nil } @@ -100,9 +113,31 @@ func (l *LimitRanger) Admit(a admission.Attributes) (err error) { } } + items, err := l.GetLimitRanges(a) + if err != nil { + return err + } + + // ensure it meets each prescribed min/max + for i := range items { + limitRange := items[i] + + if !l.actions.SupportsLimit(limitRange) { + continue + } + + err = limitFn(limitRange, a.GetResource().Resource, a.GetObject()) + if err != nil { + return admission.NewForbidden(a, err) + } + } + return nil +} + +func (l *LimitRanger) GetLimitRanges(a admission.Attributes) ([]*api.LimitRange, error) { 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())) + return nil, 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. @@ -116,7 +151,7 @@ func (l *LimitRanger) Admit(a admission.Attributes) (err error) { // throttling - see #22422 for details. liveList, err := l.client.Core().LimitRanges(a.GetNamespace()).List(metav1.ListOptions{}) if err != nil { - return admission.NewForbidden(a, err) + return nil, admission.NewForbidden(a, err) } newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)} for i := range liveList.Items { @@ -133,20 +168,7 @@ func (l *LimitRanger) Admit(a admission.Attributes) (err error) { } - // 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 + return items, nil } // NewLimitRanger returns an object that enforces limits based on the supplied limit function @@ -399,12 +421,23 @@ 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 { +func (d *DefaultLimitRangerActions) MutateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error { switch resourceName { case "pods": - return PodLimitFunc(limitRange, obj.(*api.Pod)) + return PodMutateLimitFunc(limitRange, obj.(*api.Pod)) + } + return nil +} + +// 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) ValidateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error { + switch resourceName { + case "pods": + return PodValidateLimitFunc(limitRange, obj.(*api.Pod)) case "persistentvolumeclaims": - return PersistentVolumeClaimLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim)) + return PersistentVolumeClaimValidateLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim)) } return nil } @@ -424,11 +457,11 @@ func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *api.LimitRange) bo return true } -// PersistentVolumeClaimLimitFunc enforces storage limits for PVCs. +// PersistentVolumeClaimValidateLimitFunc 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 { +func PersistentVolumeClaimValidateLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error { var errs []error for i := range limitRange.Spec.Limits { limit := limitRange.Spec.Limits[i] @@ -452,14 +485,19 @@ func PersistentVolumeClaimLimitFunc(limitRange *api.LimitRange, pvc *api.Persist return utilerrors.NewAggregate(errs) } -// PodLimitFunc enforces resource requirements enumerated by the pod against +// PodMutateLimitFunc sets 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 - +func PodMutateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error { defaultResources := defaultContainerResourceRequirements(limitRange) mergePodResourceRequirements(pod, &defaultResources) + return nil +} + +// PodValidateLimitFunc enforces resource requirements enumerated by the pod against +// the specified LimitRange. +func PodValidateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error { + var errs []error for i := range limitRange.Spec.Limits { limit := limitRange.Spec.Limits[i] diff --git a/plugin/pkg/admission/limitranger/admission_test.go b/plugin/pkg/admission/limitranger/admission_test.go index 271429f1f4e..2e864ba36d7 100644 --- a/plugin/pkg/admission/limitranger/admission_test.go +++ b/plugin/pkg/admission/limitranger/admission_test.go @@ -430,7 +430,11 @@ func TestPodLimitFunc(t *testing.T) { } for i := range successCases { test := successCases[i] - err := PodLimitFunc(&test.limitRange, &test.pod) + err := PodMutateLimitFunc(&test.limitRange, &test.pod) + if err != nil { + t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err) + } + err = PodValidateLimitFunc(&test.limitRange, &test.pod) if err != nil { t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err) } @@ -610,7 +614,11 @@ func TestPodLimitFunc(t *testing.T) { } for i := range errorCases { test := errorCases[i] - err := PodLimitFunc(&test.limitRange, &test.pod) + err := PodMutateLimitFunc(&test.limitRange, &test.pod) + if err != nil { + t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err) + } + err = PodValidateLimitFunc(&test.limitRange, &test.pod) if err == nil { t.Errorf("Expected error for pod: %s", test.pod.Name) } @@ -628,7 +636,7 @@ func getLocalStorageResourceList(ephemeralStorage string) api.ResourceList { 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) + err := PodMutateLimitFunc(&limitRange, &testPod) if err != nil { t.Errorf("Unexpected error for valid pod: %s, %v", testPod.Name, err) } @@ -687,11 +695,15 @@ func TestLimitRangerIgnoresSubresource(t *testing.T) { 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.Fatal(err) + } + err = handler.Validate(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)) + err = handler.Validate(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) } @@ -709,11 +721,15 @@ func TestLimitRangerAdmitPod(t *testing.T) { 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.Fatal(err) + } + err = handler.Validate(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)) + err = handler.Validate(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) } @@ -786,7 +802,7 @@ func TestPersistentVolumeClaimLimitFunc(t *testing.T) { } for i := range successCases { test := successCases[i] - err := PersistentVolumeClaimLimitFunc(&test.limitRange, &test.pvc) + err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc) if err != nil { t.Errorf("Unexpected error for pvc: %s, %v", test.pvc.Name, err) } @@ -804,7 +820,7 @@ func TestPersistentVolumeClaimLimitFunc(t *testing.T) { } for i := range errorCases { test := errorCases[i] - err := PersistentVolumeClaimLimitFunc(&test.limitRange, &test.pvc) + err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc) if err == nil { t.Errorf("Expected error for pvc: %s", test.pvc.Name) } diff --git a/plugin/pkg/admission/limitranger/interfaces.go b/plugin/pkg/admission/limitranger/interfaces.go index abdcbfd2ede..4c520c68480 100644 --- a/plugin/pkg/admission/limitranger/interfaces.go +++ b/plugin/pkg/admission/limitranger/interfaces.go @@ -23,8 +23,10 @@ import ( ) type LimitRangerActions interface { - // Limit is a pluggable function to enforce limits on the object. - Limit(limitRange *api.LimitRange, kind string, obj runtime.Object) error + // MutateLimit is a pluggable function to set limits on the object. + MutateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error + // ValidateLimits is a pluggable function to enforce limits on the object. + ValidateLimit(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