Skip to content

Commit a0d4878

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#30145 from markturansky/limit_range_pvc
Automatic merge from submit-queue Add PVC storage to LimitRange This PR adds the ability to add a LimitRange to a namespace that enforces min/max on `pvc.Spec.Resources.Requests["storage"]`. @derekwaynecarr @abhgupta @kubernetes/sig-storage Examples forthcoming. ```release-note pvc.Spec.Resources.Requests min and max can be enforced with a LimitRange of type "PersistentVolumeClaim" in the namespace ```
2 parents fdcfb80 + 0d40104 commit a0d4878

File tree

8 files changed

+289
-117
lines changed

8 files changed

+289
-117
lines changed

docs/design/admission_control_limit_range.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ as part of admission control.
4747
4. Ability to specify default resource limits for a container
4848
5. Ability to specify default resource requests for a container
4949
6. Ability to enforce a ratio between request and limit for a resource.
50+
7. Ability to enforce min/max storage requests for persistent volume claims
5051

5152
## Data Model
5253

@@ -209,6 +210,23 @@ Across all containers in pod, the following must hold true
209210
| Max | Limit (required) <= Max |
210211
| LimitRequestRatio | LimitRequestRatio <= ( Limit (required, non-zero) / Request (non-zero) ) |
211212

213+
**Type: PersistentVolumeClaim**
214+
215+
Supported Resources:
216+
217+
1. storage
218+
219+
Supported Constraints:
220+
221+
Across all claims in a namespace, the following must hold true:
222+
223+
| Constraint | Behavior |
224+
| ---------- | -------- |
225+
| Min | Min >= Request (required) |
226+
| Max | Max <= Request (required) |
227+
228+
Supported Defaults: None. Storage is a required field in `PersistentVolumeClaim`, so defaults are not applied at this time.
229+
212230
## Run-time configuration
213231

214232
The default ```LimitRange``` that is applied via Salt configuration will be

pkg/api/helpers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func IsStandardContainerResourceName(str string) bool {
123123
var standardLimitRangeTypes = sets.NewString(
124124
string(LimitTypePod),
125125
string(LimitTypeContainer),
126+
string(LimitTypePersistentVolumeClaim),
126127
)
127128

128129
// IsStandardLimitRangeType returns true if the type is Pod or Container

pkg/api/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,6 +2684,8 @@ const (
26842684
LimitTypePod LimitType = "Pod"
26852685
// Limit that applies to all containers in a namespace
26862686
LimitTypeContainer LimitType = "Container"
2687+
// Limit that applies to all persistent volume claims in a namespace
2688+
LimitTypePersistentVolumeClaim LimitType = "PersistentVolumeClaim"
26872689
)
26882690

26892691
// LimitRangeItem defines a min/max usage limit for any resource that matches on kind

pkg/api/v1/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3138,6 +3138,8 @@ const (
31383138
LimitTypePod LimitType = "Pod"
31393139
// Limit that applies to all containers in a namespace
31403140
LimitTypeContainer LimitType = "Container"
3141+
// Limit that applies to all persistent volume claims in a namespace
3142+
LimitTypePersistentVolumeClaim LimitType = "PersistentVolumeClaim"
31413143
)
31423144

31433145
// LimitRangeItem defines a min/max usage limit for any resource that matches on kind.

pkg/api/validation/validation.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2942,6 +2942,17 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList {
29422942
}
29432943
}
29442944

2945+
if limit.Type == api.LimitTypePersistentVolumeClaim {
2946+
_, minQuantityFound := limit.Min[api.ResourceStorage]
2947+
_, maxQuantityFound := limit.Max[api.ResourceStorage]
2948+
if !minQuantityFound {
2949+
allErrs = append(allErrs, field.Required(idxPath.Child("min"), "minimum storage value is required"))
2950+
}
2951+
if !maxQuantityFound {
2952+
allErrs = append(allErrs, field.Required(idxPath.Child("max"), "maximum storage value is required"))
2953+
}
2954+
}
2955+
29452956
for k, q := range limit.MaxLimitRequestRatio {
29462957
allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...)
29472958
keys.Insert(string(k))

pkg/api/validation/validation_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6542,6 +6542,11 @@ func TestValidateLimitRange(t *testing.T) {
65426542
DefaultRequest: getResourceList("10m", "200Mi"),
65436543
MaxLimitRequestRatio: getResourceList("10", ""),
65446544
},
6545+
{
6546+
Type: api.LimitTypePersistentVolumeClaim,
6547+
Max: getStorageResourceList("10Gi"),
6548+
Min: getStorageResourceList("5Gi"),
6549+
},
65456550
},
65466551
},
65476552
},
@@ -6752,6 +6757,40 @@ func TestValidateLimitRange(t *testing.T) {
67526757
}},
67536758
"must be a standard limit type or fully qualified",
67546759
},
6760+
"invalid missing required min field": {
6761+
api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{
6762+
Limits: []api.LimitRangeItem{
6763+
{
6764+
Type: api.LimitTypePersistentVolumeClaim,
6765+
Max: getStorageResourceList("10000T"),
6766+
},
6767+
},
6768+
}},
6769+
"minimum storage value is required",
6770+
},
6771+
"invalid missing required max field": {
6772+
api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{
6773+
Limits: []api.LimitRangeItem{
6774+
{
6775+
Type: api.LimitTypePersistentVolumeClaim,
6776+
Min: getStorageResourceList("10000T"),
6777+
},
6778+
},
6779+
}},
6780+
"maximum storage value is required",
6781+
},
6782+
"invalid min greater than max": {
6783+
api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{
6784+
Limits: []api.LimitRangeItem{
6785+
{
6786+
Type: api.LimitTypePersistentVolumeClaim,
6787+
Min: getStorageResourceList("10Gi"),
6788+
Max: getStorageResourceList("1Gi"),
6789+
},
6790+
},
6791+
}},
6792+
"min value 10Gi is greater than max value 1Gi",
6793+
},
67556794
}
67566795

67576796
for k, v := range errorCases {

plugin/pkg/admission/limitranger/admission.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -397,25 +397,55 @@ func (d *DefaultLimitRangerActions) Limit(limitRange *api.LimitRange, resourceNa
397397
switch resourceName {
398398
case "pods":
399399
return PodLimitFunc(limitRange, obj.(*api.Pod))
400+
case "persistentvolumeclaims":
401+
return PersistentVolumeClaimLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim))
400402
}
401403
return nil
402404
}
403405

404-
// SupportsAttributes ignores all calls that do not deal with pod resources since that is
405-
// all this supports now. Also ignores any call that has a subresource defined.
406+
// SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs).
407+
// Also ignores any call that has a subresource defined.
406408
func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool {
407409
if a.GetSubresource() != "" {
408410
return false
409411
}
410412

411-
return a.GetKind().GroupKind() == api.Kind("Pod")
413+
return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim")
412414
}
413415

414416
// SupportsLimit always returns true.
415417
func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *api.LimitRange) bool {
416418
return true
417419
}
418420

421+
// PersistentVolumeClaimLimitFunc enforces storage limits for PVCs.
422+
// Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange.
423+
// Claims will not be modified with default values because storage is a required part of pvc.Spec.
424+
// All storage enforced values *only* apply to pvc.Spec.Resources.Requests.
425+
func PersistentVolumeClaimLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error {
426+
var errs []error
427+
for i := range limitRange.Spec.Limits {
428+
limit := limitRange.Spec.Limits[i]
429+
limitType := limit.Type
430+
if limitType == api.LimitTypePersistentVolumeClaim {
431+
for k, v := range limit.Min {
432+
// normal usage of minConstraint. pvc.Spec.Resources.Limits is not recognized as user input
433+
if err := minConstraint(limitType, k, v, pvc.Spec.Resources.Requests, api.ResourceList{}); err != nil {
434+
errs = append(errs, err)
435+
}
436+
}
437+
for k, v := range limit.Max {
438+
// reverse usage of maxConstraint. We want to enforce the max of the LimitRange against what
439+
// the user requested.
440+
if err := maxConstraint(limitType, k, v, api.ResourceList{}, pvc.Spec.Resources.Requests); err != nil {
441+
errs = append(errs, err)
442+
}
443+
}
444+
}
445+
}
446+
return utilerrors.NewAggregate(errs)
447+
}
448+
419449
// PodLimitFunc enforces resource requirements enumerated by the pod against
420450
// the specified LimitRange. The pod may be modified to apply default resource
421451
// requirements if not specified, and enumerated on the LimitRange

0 commit comments

Comments
 (0)