k8s.io/kubernetes@v1.29.3/pkg/apis/flowcontrol/validation/validation.go (about) 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 }