split limitranger admission

This commit is contained in:
David Eads 2017-11-10 10:57:41 -05:00
parent 41fe3ed5bc
commit e42a0bab5c
3 changed files with 90 additions and 34 deletions

View File

@ -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]

View File

@ -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)
}

View File

@ -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