...

Source file src/k8s.io/kubernetes/pkg/apis/flowcontrol/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/apis/flowcontrol/validation

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	flowcontrolv1beta1 "k8s.io/api/flowcontrol/v1beta1"
    24  	flowcontrolv1beta2 "k8s.io/api/flowcontrol/v1beta2"
    25  	flowcontrolv1beta3 "k8s.io/api/flowcontrol/v1beta3"
    26  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    27  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/apimachinery/pkg/util/validation/field"
    31  	"k8s.io/apiserver/pkg/util/shufflesharding"
    32  	apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    33  	"k8s.io/kubernetes/pkg/apis/flowcontrol"
    34  	"k8s.io/kubernetes/pkg/apis/flowcontrol/internalbootstrap"
    35  )
    36  
    37  // ValidateFlowSchemaName validates name for flow-schema.
    38  var ValidateFlowSchemaName = apimachineryvalidation.NameIsDNSSubdomain
    39  
    40  // ValidatePriorityLevelConfigurationName validates name for priority-level-configuration.
    41  var ValidatePriorityLevelConfigurationName = apimachineryvalidation.NameIsDNSSubdomain
    42  
    43  var supportedDistinguisherMethods = sets.NewString(
    44  	string(flowcontrol.FlowDistinguisherMethodByNamespaceType),
    45  	string(flowcontrol.FlowDistinguisherMethodByUserType),
    46  )
    47  
    48  var priorityLevelConfigurationQueuingMaxQueues int32 = 10 * 1000 * 1000 // 10^7
    49  
    50  var supportedVerbs = sets.NewString(
    51  	"get",
    52  	"list",
    53  	"create",
    54  	"update",
    55  	"delete",
    56  	"deletecollection",
    57  	"patch",
    58  	"watch",
    59  	"proxy",
    60  )
    61  
    62  var supportedSubjectKinds = sets.NewString(
    63  	string(flowcontrol.SubjectKindServiceAccount),
    64  	string(flowcontrol.SubjectKindGroup),
    65  	string(flowcontrol.SubjectKindUser),
    66  )
    67  
    68  var supportedPriorityLevelEnablement = sets.NewString(
    69  	string(flowcontrol.PriorityLevelEnablementExempt),
    70  	string(flowcontrol.PriorityLevelEnablementLimited),
    71  )
    72  
    73  var supportedLimitResponseType = sets.NewString(
    74  	string(flowcontrol.LimitResponseTypeQueue),
    75  	string(flowcontrol.LimitResponseTypeReject),
    76  )
    77  
    78  // PriorityLevelValidationOptions holds the validation options for a priority level object
    79  type PriorityLevelValidationOptions struct {
    80  	// AllowZeroLimitedNominalConcurrencyShares, if true, indicates that we allow
    81  	// a zero value for the 'nominalConcurrencyShares' field of the 'limited'
    82  	// section of a priority level.
    83  	AllowZeroLimitedNominalConcurrencyShares bool
    84  }
    85  
    86  // ValidateFlowSchema validates the content of flow-schema
    87  func ValidateFlowSchema(fs *flowcontrol.FlowSchema) field.ErrorList {
    88  	allErrs := apivalidation.ValidateObjectMeta(&fs.ObjectMeta, false, ValidateFlowSchemaName, field.NewPath("metadata"))
    89  	specPath := field.NewPath("spec")
    90  	allErrs = append(allErrs, ValidateFlowSchemaSpec(fs.Name, &fs.Spec, specPath)...)
    91  	if mand, ok := internalbootstrap.MandatoryFlowSchemas[fs.Name]; ok {
    92  		// Check for almost exact equality.  This is a pretty
    93  		// strict test, and it is OK in this context because both
    94  		// sides of this comparison are intended to ultimately
    95  		// come from the same code.
    96  		if !apiequality.Semantic.DeepEqual(fs.Spec, mand.Spec) {
    97  			allErrs = append(allErrs, field.Invalid(specPath, fs.Spec, fmt.Sprintf("spec of '%s' must equal the fixed value", fs.Name)))
    98  		}
    99  	}
   100  	allErrs = append(allErrs, ValidateFlowSchemaStatus(&fs.Status, field.NewPath("status"))...)
   101  	return allErrs
   102  }
   103  
   104  // ValidateFlowSchemaUpdate validates the update of flow-schema
   105  func ValidateFlowSchemaUpdate(old, fs *flowcontrol.FlowSchema) field.ErrorList {
   106  	return ValidateFlowSchema(fs)
   107  }
   108  
   109  // ValidateFlowSchemaSpec validates the content of flow-schema's spec
   110  func ValidateFlowSchemaSpec(fsName string, spec *flowcontrol.FlowSchemaSpec, fldPath *field.Path) field.ErrorList {
   111  	var allErrs field.ErrorList
   112  	if spec.MatchingPrecedence <= 0 {
   113  		allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "must be a positive value"))
   114  	}
   115  	if spec.MatchingPrecedence > flowcontrol.FlowSchemaMaxMatchingPrecedence {
   116  		allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, fmt.Sprintf("must not be greater than %v", flowcontrol.FlowSchemaMaxMatchingPrecedence)))
   117  	}
   118  	if (spec.MatchingPrecedence == 1) && (fsName != flowcontrol.FlowSchemaNameExempt) {
   119  		allErrs = append(allErrs, field.Invalid(fldPath.Child("matchingPrecedence"), spec.MatchingPrecedence, "only the schema named 'exempt' may have matchingPrecedence 1"))
   120  	}
   121  	if spec.DistinguisherMethod != nil {
   122  		if !supportedDistinguisherMethods.Has(string(spec.DistinguisherMethod.Type)) {
   123  			allErrs = append(allErrs, field.NotSupported(fldPath.Child("distinguisherMethod").Child("type"), spec.DistinguisherMethod, supportedDistinguisherMethods.List()))
   124  		}
   125  	}
   126  	if len(spec.PriorityLevelConfiguration.Name) > 0 {
   127  		for _, msg := range ValidatePriorityLevelConfigurationName(spec.PriorityLevelConfiguration.Name, false) {
   128  			allErrs = append(allErrs, field.Invalid(fldPath.Child("priorityLevelConfiguration").Child("name"), spec.PriorityLevelConfiguration.Name, msg))
   129  		}
   130  	} else {
   131  		allErrs = append(allErrs, field.Required(fldPath.Child("priorityLevelConfiguration").Child("name"), "must reference a priority level"))
   132  	}
   133  	for i, rule := range spec.Rules {
   134  		allErrs = append(allErrs, ValidateFlowSchemaPolicyRulesWithSubjects(&rule, fldPath.Child("rules").Index(i))...)
   135  	}
   136  	return allErrs
   137  }
   138  
   139  // ValidateFlowSchemaPolicyRulesWithSubjects validates policy-rule-with-subjects object.
   140  func ValidateFlowSchemaPolicyRulesWithSubjects(rule *flowcontrol.PolicyRulesWithSubjects, fldPath *field.Path) field.ErrorList {
   141  	var allErrs field.ErrorList
   142  	if len(rule.Subjects) > 0 {
   143  		for i, subject := range rule.Subjects {
   144  			allErrs = append(allErrs, ValidateFlowSchemaSubject(&subject, fldPath.Child("subjects").Index(i))...)
   145  		}
   146  	} else {
   147  		allErrs = append(allErrs, field.Required(fldPath.Child("subjects"), "subjects must contain at least one value"))
   148  	}
   149  
   150  	if len(rule.ResourceRules) == 0 && len(rule.NonResourceRules) == 0 {
   151  		allErrs = append(allErrs, field.Required(fldPath, "at least one of resourceRules and nonResourceRules has to be non-empty"))
   152  	}
   153  	for i, resourceRule := range rule.ResourceRules {
   154  		allErrs = append(allErrs, ValidateFlowSchemaResourcePolicyRule(&resourceRule, fldPath.Child("resourceRules").Index(i))...)
   155  	}
   156  	for i, nonResourceRule := range rule.NonResourceRules {
   157  		allErrs = append(allErrs, ValidateFlowSchemaNonResourcePolicyRule(&nonResourceRule, fldPath.Child("nonResourceRules").Index(i))...)
   158  	}
   159  	return allErrs
   160  }
   161  
   162  // ValidateFlowSchemaSubject validates flow-schema's subject object.
   163  func ValidateFlowSchemaSubject(subject *flowcontrol.Subject, fldPath *field.Path) field.ErrorList {
   164  	var allErrs field.ErrorList
   165  	switch subject.Kind {
   166  	case flowcontrol.SubjectKindServiceAccount:
   167  		allErrs = append(allErrs, ValidateServiceAccountSubject(subject.ServiceAccount, fldPath.Child("serviceAccount"))...)
   168  		if subject.User != nil {
   169  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("user"), "user is forbidden when subject kind is not 'User'"))
   170  		}
   171  		if subject.Group != nil {
   172  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("group"), "group is forbidden when subject kind is not 'Group'"))
   173  		}
   174  	case flowcontrol.SubjectKindUser:
   175  		allErrs = append(allErrs, ValidateUserSubject(subject.User, fldPath.Child("user"))...)
   176  		if subject.ServiceAccount != nil {
   177  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"))
   178  		}
   179  		if subject.Group != nil {
   180  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("group"), "group is forbidden when subject kind is not 'Group'"))
   181  		}
   182  	case flowcontrol.SubjectKindGroup:
   183  		allErrs = append(allErrs, ValidateGroupSubject(subject.Group, fldPath.Child("group"))...)
   184  		if subject.ServiceAccount != nil {
   185  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"))
   186  		}
   187  		if subject.User != nil {
   188  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("user"), "user is forbidden when subject kind is not 'User'"))
   189  		}
   190  	default:
   191  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("kind"), subject.Kind, supportedSubjectKinds.List()))
   192  	}
   193  	return allErrs
   194  }
   195  
   196  // ValidateServiceAccountSubject validates subject of "ServiceAccount" kind
   197  func ValidateServiceAccountSubject(subject *flowcontrol.ServiceAccountSubject, fldPath *field.Path) field.ErrorList {
   198  	var allErrs field.ErrorList
   199  	if subject == nil {
   200  		return append(allErrs, field.Required(fldPath, "serviceAccount is required when subject kind is 'ServiceAccount'"))
   201  	}
   202  	if len(subject.Name) == 0 {
   203  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
   204  	} else if subject.Name != flowcontrol.NameAll {
   205  		for _, msg := range apimachineryvalidation.ValidateServiceAccountName(subject.Name, false) {
   206  			allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), subject.Name, msg))
   207  		}
   208  	}
   209  
   210  	if len(subject.Namespace) > 0 {
   211  		for _, msg := range apimachineryvalidation.ValidateNamespaceName(subject.Namespace, false) {
   212  			allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), subject.Namespace, msg))
   213  		}
   214  	} else {
   215  		allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "must specify namespace for service account"))
   216  	}
   217  
   218  	return allErrs
   219  }
   220  
   221  // ValidateUserSubject validates subject of "User" kind
   222  func ValidateUserSubject(subject *flowcontrol.UserSubject, fldPath *field.Path) field.ErrorList {
   223  	var allErrs field.ErrorList
   224  	if subject == nil {
   225  		return append(allErrs, field.Required(fldPath, "user is required when subject kind is 'User'"))
   226  	}
   227  	if len(subject.Name) == 0 {
   228  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
   229  	}
   230  	return allErrs
   231  }
   232  
   233  // ValidateGroupSubject validates subject of "Group" kind
   234  func ValidateGroupSubject(subject *flowcontrol.GroupSubject, fldPath *field.Path) field.ErrorList {
   235  	var allErrs field.ErrorList
   236  	if subject == nil {
   237  		return append(allErrs, field.Required(fldPath, "group is required when subject kind is 'Group'"))
   238  	}
   239  	if len(subject.Name) == 0 {
   240  		allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
   241  	}
   242  	return allErrs
   243  }
   244  
   245  // ValidateFlowSchemaNonResourcePolicyRule validates non-resource policy-rule in the flow-schema.
   246  func ValidateFlowSchemaNonResourcePolicyRule(rule *flowcontrol.NonResourcePolicyRule, fldPath *field.Path) field.ErrorList {
   247  	var allErrs field.ErrorList
   248  
   249  	if len(rule.Verbs) == 0 {
   250  		allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value"))
   251  	} else if hasWildcard(rule.Verbs) {
   252  		if len(rule.Verbs) > 1 {
   253  			allErrs = append(allErrs, field.Invalid(fldPath.Child("verbs"), rule.Verbs, "if '*' is present, must not specify other verbs"))
   254  		}
   255  	} else if !supportedVerbs.IsSuperset(sets.NewString(rule.Verbs...)) {
   256  		// only supported verbs are allowed
   257  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("verbs"), rule.Verbs, supportedVerbs.List()))
   258  	}
   259  
   260  	if len(rule.NonResourceURLs) == 0 {
   261  		allErrs = append(allErrs, field.Required(fldPath.Child("nonResourceURLs"), "nonResourceURLs must contain at least one value"))
   262  	} else if hasWildcard(rule.NonResourceURLs) {
   263  		if len(rule.NonResourceURLs) > 1 {
   264  			allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "if '*' is present, must not specify other non-resource URLs"))
   265  		}
   266  	} else {
   267  		for i, nonResourceURL := range rule.NonResourceURLs {
   268  			if err := ValidateNonResourceURLPath(nonResourceURL, fldPath.Child("nonResourceURLs").Index(i)); err != nil {
   269  				allErrs = append(allErrs, err)
   270  			}
   271  		}
   272  	}
   273  
   274  	return allErrs
   275  }
   276  
   277  // ValidateFlowSchemaResourcePolicyRule validates resource policy-rule in the flow-schema.
   278  func ValidateFlowSchemaResourcePolicyRule(rule *flowcontrol.ResourcePolicyRule, fldPath *field.Path) field.ErrorList {
   279  	var allErrs field.ErrorList
   280  
   281  	if len(rule.Verbs) == 0 {
   282  		allErrs = append(allErrs, field.Required(fldPath.Child("verbs"), "verbs must contain at least one value"))
   283  	} else if hasWildcard(rule.Verbs) {
   284  		if len(rule.Verbs) > 1 {
   285  			allErrs = append(allErrs, field.Invalid(fldPath.Child("verbs"), rule.Verbs, "if '*' is present, must not specify other verbs"))
   286  		}
   287  	} else if !supportedVerbs.IsSuperset(sets.NewString(rule.Verbs...)) {
   288  		// only supported verbs are allowed
   289  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("verbs"), rule.Verbs, supportedVerbs.List()))
   290  	}
   291  
   292  	if len(rule.APIGroups) == 0 {
   293  		allErrs = append(allErrs, field.Required(fldPath.Child("apiGroups"), "resource rules must supply at least one api group"))
   294  	} else if len(rule.APIGroups) > 1 && hasWildcard(rule.APIGroups) {
   295  		allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroups"), rule.APIGroups, "if '*' is present, must not specify other api groups"))
   296  	}
   297  
   298  	if len(rule.Resources) == 0 {
   299  		allErrs = append(allErrs, field.Required(fldPath.Child("resources"), "resource rules must supply at least one resource"))
   300  	} else if len(rule.Resources) > 1 && hasWildcard(rule.Resources) {
   301  		allErrs = append(allErrs, field.Invalid(fldPath.Child("resources"), rule.Resources, "if '*' is present, must not specify other resources"))
   302  	}
   303  
   304  	if len(rule.Namespaces) == 0 && !rule.ClusterScope {
   305  		allErrs = append(allErrs, field.Required(fldPath.Child("namespaces"), "resource rules that are not cluster scoped must supply at least one namespace"))
   306  	} else if hasWildcard(rule.Namespaces) {
   307  		if len(rule.Namespaces) > 1 {
   308  			allErrs = append(allErrs, field.Invalid(fldPath.Child("namespaces"), rule.Namespaces, "if '*' is present, must not specify other namespaces"))
   309  		}
   310  	} else {
   311  		for idx, tgtNS := range rule.Namespaces {
   312  			for _, msg := range apimachineryvalidation.ValidateNamespaceName(tgtNS, false) {
   313  				allErrs = append(allErrs, field.Invalid(fldPath.Child("namespaces").Index(idx), tgtNS, nsErrIntro+msg))
   314  			}
   315  		}
   316  	}
   317  
   318  	return allErrs
   319  }
   320  
   321  const nsErrIntro = "each member of this list must be '*' or a DNS-1123 label; "
   322  
   323  // ValidateFlowSchemaStatus validates status for the flow-schema.
   324  func ValidateFlowSchemaStatus(status *flowcontrol.FlowSchemaStatus, fldPath *field.Path) field.ErrorList {
   325  	var allErrs field.ErrorList
   326  	keys := sets.NewString()
   327  	for i, condition := range status.Conditions {
   328  		if keys.Has(string(condition.Type)) {
   329  			allErrs = append(allErrs, field.Duplicate(fldPath.Child("conditions").Index(i).Child("type"), condition.Type))
   330  		}
   331  		keys.Insert(string(condition.Type))
   332  		allErrs = append(allErrs, ValidateFlowSchemaCondition(&condition, fldPath.Child("conditions").Index(i))...)
   333  	}
   334  	return allErrs
   335  }
   336  
   337  // ValidateFlowSchemaStatusUpdate validates the update of status for the flow-schema.
   338  func ValidateFlowSchemaStatusUpdate(old, fs *flowcontrol.FlowSchema) field.ErrorList {
   339  	return ValidateFlowSchemaStatus(&fs.Status, field.NewPath("status"))
   340  }
   341  
   342  // ValidateFlowSchemaCondition validates condition in the flow-schema's status.
   343  func ValidateFlowSchemaCondition(condition *flowcontrol.FlowSchemaCondition, fldPath *field.Path) field.ErrorList {
   344  	var allErrs field.ErrorList
   345  	if len(condition.Type) == 0 {
   346  		allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty"))
   347  	}
   348  	return allErrs
   349  }
   350  
   351  // ValidatePriorityLevelConfiguration validates priority-level-configuration.
   352  func ValidatePriorityLevelConfiguration(pl *flowcontrol.PriorityLevelConfiguration, requestGV schema.GroupVersion, opts PriorityLevelValidationOptions) field.ErrorList {
   353  	allErrs := apivalidation.ValidateObjectMeta(&pl.ObjectMeta, false, ValidatePriorityLevelConfigurationName, field.NewPath("metadata"))
   354  
   355  	// the roundtrip annotation is only for use in v1beta3, and after
   356  	// conversion, the internal object should not have the roundtrip
   357  	// annotation, so we should forbid it, if it's set.
   358  	if _, ok := pl.ObjectMeta.Annotations[flowcontrolv1beta3.PriorityLevelPreserveZeroConcurrencySharesKey]; ok {
   359  		allErrs = append(allErrs, field.Forbidden(field.NewPath("metadata").Child("annotations"), fmt.Sprintf("annotation '%s' is forbidden", flowcontrolv1beta3.PriorityLevelPreserveZeroConcurrencySharesKey)))
   360  	}
   361  
   362  	specPath := field.NewPath("spec")
   363  	allErrs = append(allErrs, ValidatePriorityLevelConfigurationSpec(&pl.Spec, requestGV, pl.Name, specPath, opts)...)
   364  	allErrs = append(allErrs, ValidateIfMandatoryPriorityLevelConfigurationObject(pl, specPath)...)
   365  	allErrs = append(allErrs, ValidatePriorityLevelConfigurationStatus(&pl.Status, field.NewPath("status"))...)
   366  	return allErrs
   367  }
   368  
   369  func ValidateIfMandatoryPriorityLevelConfigurationObject(pl *flowcontrol.PriorityLevelConfiguration, fldPath *field.Path) field.ErrorList {
   370  	var allErrs field.ErrorList
   371  	mand, ok := internalbootstrap.MandatoryPriorityLevelConfigurations[pl.Name]
   372  	if !ok {
   373  		return allErrs
   374  	}
   375  
   376  	if pl.Name == flowcontrol.PriorityLevelConfigurationNameExempt {
   377  		// we allow the admin to change the contents of the 'Exempt' field of
   378  		// the singleton 'exempt' priority level object, every other fields of
   379  		// the Spec should not be allowed to change.
   380  		want := &mand.Spec
   381  		have := pl.Spec.DeepCopy()
   382  		have.Exempt = want.Exempt
   383  		if !apiequality.Semantic.DeepEqual(want, have) {
   384  			allErrs = append(allErrs, field.Invalid(fldPath, pl.Spec, fmt.Sprintf("spec of '%s' except the 'spec.exempt' field must equal the fixed value", pl.Name)))
   385  		}
   386  		return allErrs
   387  	}
   388  
   389  	// Check for almost exact equality.  This is a pretty
   390  	// strict test, and it is OK in this context because both
   391  	// sides of this comparison are intended to ultimately
   392  	// come from the same code.
   393  	if !apiequality.Semantic.DeepEqual(pl.Spec, mand.Spec) {
   394  		allErrs = append(allErrs, field.Invalid(fldPath, pl.Spec, fmt.Sprintf("spec of '%s' must equal the fixed value", pl.Name)))
   395  	}
   396  	return allErrs
   397  }
   398  
   399  // ValidatePriorityLevelConfigurationSpec validates priority-level-configuration's spec.
   400  func ValidatePriorityLevelConfigurationSpec(spec *flowcontrol.PriorityLevelConfigurationSpec, requestGV schema.GroupVersion, name string, fldPath *field.Path, opts PriorityLevelValidationOptions) field.ErrorList {
   401  	var allErrs field.ErrorList
   402  	if (name == flowcontrol.PriorityLevelConfigurationNameExempt) != (spec.Type == flowcontrol.PriorityLevelEnablementExempt) {
   403  		allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), spec.Type, "type must be 'Exempt' if and only if name is 'exempt'"))
   404  	}
   405  	switch spec.Type {
   406  	case flowcontrol.PriorityLevelEnablementExempt:
   407  		if spec.Limited != nil {
   408  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("limited"), "must be nil if the type is not Limited"))
   409  		}
   410  		if spec.Exempt != nil {
   411  			allErrs = append(allErrs, ValidateExemptPriorityLevelConfiguration(spec.Exempt, fldPath.Child("exempt"))...)
   412  		}
   413  	case flowcontrol.PriorityLevelEnablementLimited:
   414  		if spec.Exempt != nil {
   415  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("exempt"), "must be nil if the type is Limited"))
   416  		}
   417  
   418  		if spec.Limited == nil {
   419  			allErrs = append(allErrs, field.Required(fldPath.Child("limited"), "must not be empty when type is Limited"))
   420  		} else {
   421  			allErrs = append(allErrs, ValidateLimitedPriorityLevelConfiguration(spec.Limited, requestGV, fldPath.Child("limited"), opts)...)
   422  		}
   423  	default:
   424  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), spec.Type, supportedPriorityLevelEnablement.List()))
   425  	}
   426  	return allErrs
   427  }
   428  
   429  // ValidateLimitedPriorityLevelConfiguration validates the configuration for an execution-limited priority level
   430  func ValidateLimitedPriorityLevelConfiguration(lplc *flowcontrol.LimitedPriorityLevelConfiguration, requestGV schema.GroupVersion, fldPath *field.Path, opts PriorityLevelValidationOptions) field.ErrorList {
   431  	var allErrs field.ErrorList
   432  	if opts.AllowZeroLimitedNominalConcurrencyShares {
   433  		if lplc.NominalConcurrencyShares < 0 {
   434  			allErrs = append(allErrs, field.Invalid(fldPath.Child(getVersionedFieldNameForConcurrencyShares(requestGV)), lplc.NominalConcurrencyShares, "must be a non-negative integer"))
   435  		}
   436  	} else {
   437  		if lplc.NominalConcurrencyShares <= 0 {
   438  			allErrs = append(allErrs, field.Invalid(fldPath.Child(getVersionedFieldNameForConcurrencyShares(requestGV)), lplc.NominalConcurrencyShares, "must be positive"))
   439  		}
   440  	}
   441  	allErrs = append(allErrs, ValidateLimitResponse(lplc.LimitResponse, fldPath.Child("limitResponse"))...)
   442  
   443  	if lplc.LendablePercent != nil && !(*lplc.LendablePercent >= 0 && *lplc.LendablePercent <= 100) {
   444  		allErrs = append(allErrs, field.Invalid(fldPath.Child("lendablePercent"), *lplc.LendablePercent, "must be between 0 and 100, inclusive"))
   445  	}
   446  	if lplc.BorrowingLimitPercent != nil && *lplc.BorrowingLimitPercent < 0 {
   447  		allErrs = append(allErrs, field.Invalid(fldPath.Child("borrowingLimitPercent"), *lplc.BorrowingLimitPercent, "if specified, must be a non-negative integer"))
   448  	}
   449  
   450  	return allErrs
   451  }
   452  
   453  func ValidateExemptPriorityLevelConfiguration(eplc *flowcontrol.ExemptPriorityLevelConfiguration, fldPath *field.Path) field.ErrorList {
   454  	var allErrs field.ErrorList
   455  	if eplc.NominalConcurrencyShares != nil && *eplc.NominalConcurrencyShares < 0 {
   456  		allErrs = append(allErrs, field.Invalid(fldPath.Child("nominalConcurrencyShares"), *eplc.NominalConcurrencyShares, "must be a non-negative integer"))
   457  	}
   458  	if eplc.LendablePercent != nil && !(*eplc.LendablePercent >= 0 && *eplc.LendablePercent <= 100) {
   459  		allErrs = append(allErrs, field.Invalid(fldPath.Child("lendablePercent"), *eplc.LendablePercent, "must be between 0 and 100, inclusive"))
   460  	}
   461  	return allErrs
   462  }
   463  
   464  func getVersionedFieldNameForConcurrencyShares(requestGV schema.GroupVersion) string {
   465  	switch {
   466  	case requestGV == flowcontrolv1beta1.SchemeGroupVersion ||
   467  		requestGV == flowcontrolv1beta2.SchemeGroupVersion:
   468  		return "assuredConcurrencyShares"
   469  	default:
   470  		return "nominalConcurrencyShares"
   471  	}
   472  }
   473  
   474  // ValidateLimitResponse validates a LimitResponse
   475  func ValidateLimitResponse(lr flowcontrol.LimitResponse, fldPath *field.Path) field.ErrorList {
   476  	var allErrs field.ErrorList
   477  	switch lr.Type {
   478  	case flowcontrol.LimitResponseTypeReject:
   479  		if lr.Queuing != nil {
   480  			allErrs = append(allErrs, field.Forbidden(fldPath.Child("queuing"), "must be nil if limited.limitResponse.type is not Limited"))
   481  		}
   482  	case flowcontrol.LimitResponseTypeQueue:
   483  		if lr.Queuing == nil {
   484  			allErrs = append(allErrs, field.Required(fldPath.Child("queuing"), "must not be empty if limited.limitResponse.type is Limited"))
   485  		} else {
   486  			allErrs = append(allErrs, ValidatePriorityLevelQueuingConfiguration(lr.Queuing, fldPath.Child("queuing"))...)
   487  		}
   488  	default:
   489  		allErrs = append(allErrs, field.NotSupported(fldPath.Child("type"), lr.Type, supportedLimitResponseType.List()))
   490  	}
   491  	return allErrs
   492  }
   493  
   494  // ValidatePriorityLevelQueuingConfiguration validates queuing-configuration for a priority-level
   495  func ValidatePriorityLevelQueuingConfiguration(queuing *flowcontrol.QueuingConfiguration, fldPath *field.Path) field.ErrorList {
   496  	var allErrs field.ErrorList
   497  	if queuing.QueueLengthLimit <= 0 {
   498  		allErrs = append(allErrs, field.Invalid(fldPath.Child("queueLengthLimit"), queuing.QueueLengthLimit, "must be positive"))
   499  	}
   500  
   501  	// validate input arguments for shuffle-sharding
   502  	if queuing.Queues <= 0 {
   503  		allErrs = append(allErrs, field.Invalid(fldPath.Child("queues"), queuing.Queues, "must be positive"))
   504  	} else if queuing.Queues > priorityLevelConfigurationQueuingMaxQueues {
   505  		allErrs = append(allErrs, field.Invalid(fldPath.Child("queues"), queuing.Queues,
   506  			fmt.Sprintf("must not be greater than %d", priorityLevelConfigurationQueuingMaxQueues)))
   507  	}
   508  
   509  	if queuing.HandSize <= 0 {
   510  		allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), queuing.HandSize, "must be positive"))
   511  	} else if queuing.HandSize > queuing.Queues {
   512  		allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), queuing.HandSize,
   513  			fmt.Sprintf("should not be greater than queues (%d)", queuing.Queues)))
   514  	} else if entropy := shufflesharding.RequiredEntropyBits(int(queuing.Queues), int(queuing.HandSize)); entropy > shufflesharding.MaxHashBits {
   515  		allErrs = append(allErrs, field.Invalid(fldPath.Child("handSize"), queuing.HandSize,
   516  			fmt.Sprintf("required entropy bits of deckSize %d and handSize %d should not be greater than %d", queuing.Queues, queuing.HandSize, shufflesharding.MaxHashBits)))
   517  	}
   518  	return allErrs
   519  }
   520  
   521  // ValidatePriorityLevelConfigurationStatus validates priority-level-configuration's status.
   522  func ValidatePriorityLevelConfigurationStatus(status *flowcontrol.PriorityLevelConfigurationStatus, fldPath *field.Path) field.ErrorList {
   523  	var allErrs field.ErrorList
   524  	keys := sets.NewString()
   525  	for i, condition := range status.Conditions {
   526  		if keys.Has(string(condition.Type)) {
   527  			allErrs = append(allErrs, field.Duplicate(fldPath.Child("conditions").Index(i).Child("type"), condition.Type))
   528  		}
   529  		keys.Insert(string(condition.Type))
   530  		allErrs = append(allErrs, ValidatePriorityLevelConfigurationCondition(&condition, fldPath.Child("conditions").Index(i))...)
   531  	}
   532  	return allErrs
   533  }
   534  
   535  // ValidatePriorityLevelConfigurationStatusUpdate validates the update of priority-level-configuration's status.
   536  func ValidatePriorityLevelConfigurationStatusUpdate(old, pl *flowcontrol.PriorityLevelConfiguration) field.ErrorList {
   537  	return ValidatePriorityLevelConfigurationStatus(&pl.Status, field.NewPath("status"))
   538  }
   539  
   540  // ValidatePriorityLevelConfigurationCondition validates condition in priority-level-configuration's status.
   541  func ValidatePriorityLevelConfigurationCondition(condition *flowcontrol.PriorityLevelConfigurationCondition, fldPath *field.Path) field.ErrorList {
   542  	var allErrs field.ErrorList
   543  	if len(condition.Type) == 0 {
   544  		allErrs = append(allErrs, field.Required(fldPath.Child("type"), "must not be empty"))
   545  	}
   546  	return allErrs
   547  }
   548  
   549  // ValidateNonResourceURLPath validates non-resource-url path by following rules:
   550  //  1. Slash must be the leading character of the path
   551  //  2. White-space is forbidden in the path
   552  //  3. Continuous/double slash is forbidden in the path
   553  //  4. Wildcard "*" should only do suffix glob matching. Note that wildcard also matches slashes.
   554  func ValidateNonResourceURLPath(path string, fldPath *field.Path) *field.Error {
   555  	if len(path) == 0 {
   556  		return field.Invalid(fldPath, path, "must not be empty")
   557  	}
   558  	if path == "/" { // root path
   559  		return nil
   560  	}
   561  
   562  	if !strings.HasPrefix(path, "/") {
   563  		return field.Invalid(fldPath, path, "must start with slash")
   564  	}
   565  	if strings.Contains(path, " ") {
   566  		return field.Invalid(fldPath, path, "must not contain white-space")
   567  	}
   568  	if strings.Contains(path, "//") {
   569  		return field.Invalid(fldPath, path, "must not contain double slash")
   570  	}
   571  	wildcardCount := strings.Count(path, "*")
   572  	if wildcardCount > 1 || (wildcardCount == 1 && path[len(path)-2:] != "/*") {
   573  		return field.Invalid(fldPath, path, "wildcard can only do suffix matching")
   574  	}
   575  	return nil
   576  }
   577  
   578  func hasWildcard(operations []string) bool {
   579  	return memberInList("*", operations...)
   580  }
   581  
   582  func memberInList(seek string, a ...string) bool {
   583  	for _, ai := range a {
   584  		if ai == seek {
   585  			return true
   586  		}
   587  	}
   588  	return false
   589  }
   590  

View as plain text