diff --git a/pkg/api/helpers.go b/pkg/api/helpers.go index 56787767cdb..aed3c5a7aa3 100644 --- a/pkg/api/helpers.go +++ b/pkg/api/helpers.go @@ -127,6 +127,16 @@ func IsStandardContainerResourceName(str string) bool { return standardContainerResources.Has(str) } +var standardLimitRangeTypes = sets.NewString( + string(LimitTypePod), + string(LimitTypeContainer), +) + +// IsStandardLimitRangeType returns true if the type is Pod or Container +func IsStandardLimitRangeType(str string) bool { + return standardLimitRangeTypes.Has(str) +} + var standardQuotaResources = sets.NewString( string(ResourceCPU), string(ResourceMemory), diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index d0f6426d92f..fed8a128445 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -2034,6 +2034,33 @@ func validateResourceQuotaResourceName(value string, fldPath *field.Path) field. return field.ErrorList{} } +// Validate limit range types +func validateLimitRangeTypeName(value string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if !validation.IsQualifiedName(value) { + return append(allErrs, field.Invalid(fldPath, value, qualifiedNameErrorMsg)) + } + + if len(strings.Split(value, "/")) == 1 { + if !api.IsStandardLimitRangeType(value) { + return append(allErrs, field.Invalid(fldPath, value, "must be a standard limit type or fully qualified")) + } + } + + return allErrs +} + +// Validate limit range resource name +// limit types (other than Pod/Container) could contain storage not just cpu or memory +func validateLimitRangeResourceName(limitType api.LimitType, value string, fldPath *field.Path) field.ErrorList { + switch limitType { + case api.LimitTypePod, api.LimitTypeContainer: + return validateContainerResourceName(value, fldPath) + default: + return validateResourceName(value, fldPath) + } +} + // ValidateLimitRange tests if required fields in the LimitRange are set. func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { allErrs := ValidateObjectMeta(&limitRange.ObjectMeta, true, ValidateLimitRangeName, field.NewPath("metadata")) @@ -2044,6 +2071,8 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { for i := range limitRange.Spec.Limits { idxPath := fldPath.Index(i) limit := &limitRange.Spec.Limits[i] + allErrs = append(allErrs, validateLimitRangeTypeName(string(limit.Type), idxPath.Child("type"))...) + _, found := limitTypeSet[limit.Type] if found { allErrs = append(allErrs, field.Duplicate(idxPath.Child("type"), limit.Type)) @@ -2058,12 +2087,12 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { maxLimitRequestRatios := map[string]resource.Quantity{} for k, q := range limit.Max { - allErrs = append(allErrs, validateContainerResourceName(string(k), idxPath.Child("max").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("max").Key(string(k)))...) keys.Insert(string(k)) max[string(k)] = q } for k, q := range limit.Min { - allErrs = append(allErrs, validateContainerResourceName(string(k), idxPath.Child("min").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("min").Key(string(k)))...) keys.Insert(string(k)) min[string(k)] = q } @@ -2077,19 +2106,19 @@ func ValidateLimitRange(limitRange *api.LimitRange) field.ErrorList { } } else { for k, q := range limit.Default { - allErrs = append(allErrs, validateContainerResourceName(string(k), idxPath.Child("default").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("default").Key(string(k)))...) keys.Insert(string(k)) defaults[string(k)] = q } for k, q := range limit.DefaultRequest { - allErrs = append(allErrs, validateContainerResourceName(string(k), idxPath.Child("defaultRequest").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("defaultRequest").Key(string(k)))...) keys.Insert(string(k)) defaultRequests[string(k)] = q } } for k, q := range limit.MaxLimitRequestRatio { - allErrs = append(allErrs, validateContainerResourceName(string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) + allErrs = append(allErrs, validateLimitRangeResourceName(limit.Type, string(k), idxPath.Child("maxLimitRequestRatio").Key(string(k)))...) keys.Insert(string(k)) maxLimitRequestRatios[string(k)] = q } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 851e0a0dcc5..ad10d7917cf 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -3864,6 +3864,14 @@ func getResourceList(cpu, memory string) api.ResourceList { return res } +func getStorageResourceList(storage string) api.ResourceList { + res := api.ResourceList{} + if storage != "" { + res[api.ResourceStorage] = resource.MustParse(storage) + } + return res +} + func TestValidateLimitRange(t *testing.T) { successCases := []struct { name string @@ -3905,6 +3913,36 @@ func TestValidateLimitRange(t *testing.T) { }, }, }, + { + name: "thirdparty-fields-all-valid-standard-container-resources", + spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: "thirdparty.com/foo", + Max: getResourceList("100m", "10000T"), + Min: getResourceList("5m", "100Mi"), + Default: getResourceList("50m", "500Mi"), + DefaultRequest: getResourceList("10m", "200Mi"), + MaxLimitRequestRatio: getResourceList("10", ""), + }, + }, + }, + }, + { + name: "thirdparty-fields-all-valid-storage-resources", + spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: "thirdparty.com/foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }, + }, + }, + }, } for _, successCase := range successCases { @@ -4052,6 +4090,21 @@ func TestValidateLimitRange(t *testing.T) { }}, "ratio 10 is greater than max/min = 4.000000", }, + "invalid non standard limit type": { + api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ + Limits: []api.LimitRangeItem{ + { + Type: "foo", + Max: getStorageResourceList("10000T"), + Min: getStorageResourceList("100Mi"), + Default: getStorageResourceList("500Mi"), + DefaultRequest: getStorageResourceList("200Mi"), + MaxLimitRequestRatio: getStorageResourceList(""), + }, + }, + }}, + "must be a standard limit type or fully qualified", + }, } for k, v := range errorCases {