k8s.io/kubernetes@v1.29.3/pkg/apis/flowcontrol/validation/validation_test.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 "math" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/stretchr/testify/assert" 26 flowcontrolv1 "k8s.io/api/flowcontrol/v1" 27 flowcontrolv1beta1 "k8s.io/api/flowcontrol/v1beta1" 28 flowcontrolv1beta2 "k8s.io/api/flowcontrol/v1beta2" 29 flowcontrolv1beta3 "k8s.io/api/flowcontrol/v1beta3" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/util/validation/field" 33 "k8s.io/apiserver/pkg/authentication/user" 34 "k8s.io/kubernetes/pkg/apis/flowcontrol" 35 "k8s.io/kubernetes/pkg/apis/flowcontrol/internalbootstrap" 36 "k8s.io/utils/pointer" 37 ) 38 39 func TestFlowSchemaValidation(t *testing.T) { 40 badExempt := flowcontrol.FlowSchemaSpec{ 41 MatchingPrecedence: 1, 42 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 43 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 44 }, 45 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 46 Subjects: []flowcontrol.Subject{{ 47 Kind: flowcontrol.SubjectKindGroup, 48 Group: &flowcontrol.GroupSubject{Name: "system:masters"}, 49 }}, 50 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 51 Verbs: []string{flowcontrol.VerbAll}, 52 APIGroups: []string{flowcontrol.APIGroupAll}, 53 Resources: []string{flowcontrol.ResourceAll}, 54 ClusterScope: true, 55 Namespaces: []string{flowcontrol.NamespaceEvery}, 56 }}, 57 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 58 Verbs: []string{flowcontrol.VerbAll}, 59 NonResourceURLs: []string{"/"}, 60 }}, 61 }}, 62 } 63 badCatchAll := flowcontrol.FlowSchemaSpec{ 64 MatchingPrecedence: flowcontrol.FlowSchemaMaxMatchingPrecedence, 65 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 66 Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, 67 }, 68 DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, 69 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 70 Subjects: []flowcontrol.Subject{{ 71 Kind: flowcontrol.SubjectKindGroup, 72 Group: &flowcontrol.GroupSubject{Name: user.AllUnauthenticated}, 73 }, { 74 Kind: flowcontrol.SubjectKindGroup, 75 Group: &flowcontrol.GroupSubject{Name: user.AllAuthenticated}, 76 }}, 77 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 78 Verbs: []string{flowcontrol.VerbAll}, 79 APIGroups: []string{flowcontrol.APIGroupAll}, 80 Resources: []string{flowcontrol.ResourceAll}, 81 ClusterScope: true, 82 Namespaces: []string{flowcontrol.NamespaceEvery}, 83 }}, 84 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 85 Verbs: []string{flowcontrol.VerbAll}, 86 NonResourceURLs: []string{"/"}, 87 }}, 88 }}, 89 } 90 testCases := []struct { 91 name string 92 flowSchema *flowcontrol.FlowSchema 93 expectedErrors field.ErrorList 94 }{{ 95 name: "missing both resource and non-resource policy-rule should fail", 96 flowSchema: &flowcontrol.FlowSchema{ 97 ObjectMeta: metav1.ObjectMeta{ 98 Name: "system-foo", 99 }, 100 Spec: flowcontrol.FlowSchemaSpec{ 101 MatchingPrecedence: 50, 102 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 103 Name: "system-bar", 104 }, 105 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 106 Subjects: []flowcontrol.Subject{{ 107 Kind: flowcontrol.SubjectKindUser, 108 User: &flowcontrol.UserSubject{Name: "noxu"}, 109 }}, 110 }}, 111 }, 112 }, 113 expectedErrors: field.ErrorList{ 114 field.Required(field.NewPath("spec").Child("rules").Index(0), "at least one of resourceRules and nonResourceRules has to be non-empty"), 115 }, 116 }, { 117 name: "normal flow-schema w/ * verbs/apiGroups/resources should work", 118 flowSchema: &flowcontrol.FlowSchema{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: "system-foo", 121 }, 122 Spec: flowcontrol.FlowSchemaSpec{ 123 MatchingPrecedence: 50, 124 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 125 Name: "system-bar", 126 }, 127 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 128 Subjects: []flowcontrol.Subject{{ 129 Kind: flowcontrol.SubjectKindGroup, 130 Group: &flowcontrol.GroupSubject{Name: "noxu"}, 131 }}, 132 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 133 Verbs: []string{flowcontrol.VerbAll}, 134 APIGroups: []string{flowcontrol.APIGroupAll}, 135 Resources: []string{flowcontrol.ResourceAll}, 136 Namespaces: []string{flowcontrol.NamespaceEvery}, 137 }}, 138 }}, 139 }, 140 }, 141 expectedErrors: field.ErrorList{}, 142 }, { 143 name: "malformed Subject union in ServiceAccount case", 144 flowSchema: &flowcontrol.FlowSchema{ 145 ObjectMeta: metav1.ObjectMeta{ 146 Name: "system-foo", 147 }, 148 Spec: flowcontrol.FlowSchemaSpec{ 149 MatchingPrecedence: 50, 150 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 151 Name: "system-bar", 152 }, 153 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 154 Subjects: []flowcontrol.Subject{{ 155 Kind: flowcontrol.SubjectKindServiceAccount, 156 User: &flowcontrol.UserSubject{Name: "fred"}, 157 Group: &flowcontrol.GroupSubject{Name: "fred"}, 158 }}, 159 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 160 Verbs: []string{flowcontrol.VerbAll}, 161 NonResourceURLs: []string{"*"}, 162 }}, 163 }}, 164 }, 165 }, 166 expectedErrors: field.ErrorList{ 167 field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is required when subject kind is 'ServiceAccount'"), 168 field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is forbidden when subject kind is not 'User'"), 169 field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is forbidden when subject kind is not 'Group'"), 170 }, 171 }, { 172 name: "Subject union malformed in User case", 173 flowSchema: &flowcontrol.FlowSchema{ 174 ObjectMeta: metav1.ObjectMeta{ 175 Name: "system-foo", 176 }, 177 Spec: flowcontrol.FlowSchemaSpec{ 178 MatchingPrecedence: 50, 179 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 180 Name: "system-bar", 181 }, 182 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 183 Subjects: []flowcontrol.Subject{{ 184 Kind: flowcontrol.SubjectKindUser, 185 Group: &flowcontrol.GroupSubject{Name: "fred"}, 186 ServiceAccount: &flowcontrol.ServiceAccountSubject{Namespace: "s", Name: "n"}, 187 }}, 188 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 189 Verbs: []string{flowcontrol.VerbAll}, 190 NonResourceURLs: []string{"*"}, 191 }}, 192 }}, 193 }, 194 }, 195 expectedErrors: field.ErrorList{ 196 field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"), 197 field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is required when subject kind is 'User'"), 198 field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is forbidden when subject kind is not 'Group'"), 199 }, 200 }, { 201 name: "malformed Subject union in Group case", 202 flowSchema: &flowcontrol.FlowSchema{ 203 ObjectMeta: metav1.ObjectMeta{ 204 Name: "system-foo", 205 }, 206 Spec: flowcontrol.FlowSchemaSpec{ 207 MatchingPrecedence: 50, 208 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 209 Name: "system-bar", 210 }, 211 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 212 Subjects: []flowcontrol.Subject{{ 213 Kind: flowcontrol.SubjectKindGroup, 214 User: &flowcontrol.UserSubject{Name: "fred"}, 215 ServiceAccount: &flowcontrol.ServiceAccountSubject{Namespace: "s", Name: "n"}, 216 }}, 217 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 218 Verbs: []string{flowcontrol.VerbAll}, 219 NonResourceURLs: []string{"*"}, 220 }}, 221 }}, 222 }, 223 }, 224 expectedErrors: field.ErrorList{ 225 field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount"), "serviceAccount is forbidden when subject kind is not 'ServiceAccount'"), 226 field.Forbidden(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("user"), "user is forbidden when subject kind is not 'User'"), 227 field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("group"), "group is required when subject kind is 'Group'"), 228 }, 229 }, { 230 name: "exempt flow-schema should work", 231 flowSchema: &flowcontrol.FlowSchema{ 232 ObjectMeta: metav1.ObjectMeta{ 233 Name: flowcontrol.FlowSchemaNameExempt, 234 }, 235 Spec: flowcontrol.FlowSchemaSpec{ 236 MatchingPrecedence: 1, 237 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 238 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 239 }, 240 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 241 Subjects: []flowcontrol.Subject{{ 242 Kind: flowcontrol.SubjectKindGroup, 243 Group: &flowcontrol.GroupSubject{Name: "system:masters"}, 244 }}, 245 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 246 Verbs: []string{flowcontrol.VerbAll}, 247 APIGroups: []string{flowcontrol.APIGroupAll}, 248 Resources: []string{flowcontrol.ResourceAll}, 249 ClusterScope: true, 250 Namespaces: []string{flowcontrol.NamespaceEvery}, 251 }}, 252 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 253 Verbs: []string{flowcontrol.VerbAll}, 254 NonResourceURLs: []string{"*"}, 255 }}, 256 }}, 257 }, 258 }, 259 expectedErrors: field.ErrorList{}, 260 }, { 261 name: "bad exempt flow-schema should fail", 262 flowSchema: &flowcontrol.FlowSchema{ 263 ObjectMeta: metav1.ObjectMeta{ 264 Name: flowcontrol.FlowSchemaNameExempt, 265 }, 266 Spec: badExempt, 267 }, 268 expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badExempt, "spec of 'exempt' must equal the fixed value")}, 269 }, { 270 name: "bad catch-all flow-schema should fail", 271 flowSchema: &flowcontrol.FlowSchema{ 272 ObjectMeta: metav1.ObjectMeta{ 273 Name: flowcontrol.FlowSchemaNameCatchAll, 274 }, 275 Spec: badCatchAll, 276 }, 277 expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badCatchAll, "spec of 'catch-all' must equal the fixed value")}, 278 }, { 279 name: "catch-all flow-schema should work", 280 flowSchema: &flowcontrol.FlowSchema{ 281 ObjectMeta: metav1.ObjectMeta{ 282 Name: flowcontrol.FlowSchemaNameCatchAll, 283 }, 284 Spec: flowcontrol.FlowSchemaSpec{ 285 MatchingPrecedence: 10000, 286 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 287 Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, 288 }, 289 DistinguisherMethod: &flowcontrol.FlowDistinguisherMethod{Type: flowcontrol.FlowDistinguisherMethodByUserType}, 290 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 291 Subjects: []flowcontrol.Subject{{ 292 Kind: flowcontrol.SubjectKindGroup, 293 Group: &flowcontrol.GroupSubject{Name: user.AllUnauthenticated}, 294 }, { 295 Kind: flowcontrol.SubjectKindGroup, 296 Group: &flowcontrol.GroupSubject{Name: user.AllAuthenticated}, 297 }}, 298 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 299 Verbs: []string{flowcontrol.VerbAll}, 300 APIGroups: []string{flowcontrol.APIGroupAll}, 301 Resources: []string{flowcontrol.ResourceAll}, 302 ClusterScope: true, 303 Namespaces: []string{flowcontrol.NamespaceEvery}, 304 }}, 305 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 306 Verbs: []string{flowcontrol.VerbAll}, 307 NonResourceURLs: []string{"*"}, 308 }}, 309 }}, 310 }, 311 }, 312 expectedErrors: field.ErrorList{}, 313 }, { 314 name: "non-exempt flow-schema with matchingPrecedence==1 should fail", 315 flowSchema: &flowcontrol.FlowSchema{ 316 ObjectMeta: metav1.ObjectMeta{ 317 Name: "fred", 318 }, 319 Spec: flowcontrol.FlowSchemaSpec{ 320 MatchingPrecedence: 1, 321 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 322 Name: "exempt", 323 }, 324 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 325 Subjects: []flowcontrol.Subject{{ 326 Kind: flowcontrol.SubjectKindGroup, 327 Group: &flowcontrol.GroupSubject{Name: "gorp"}, 328 }}, 329 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 330 Verbs: []string{flowcontrol.VerbAll}, 331 NonResourceURLs: []string{"*"}, 332 }}, 333 }}, 334 }, 335 }, 336 expectedErrors: field.ErrorList{ 337 field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(1), "only the schema named 'exempt' may have matchingPrecedence 1")}, 338 }, { 339 name: "flow-schema mixes * verbs/apiGroups/resources should fail", 340 flowSchema: &flowcontrol.FlowSchema{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Name: "system-foo", 343 }, 344 Spec: flowcontrol.FlowSchemaSpec{ 345 MatchingPrecedence: 50, 346 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 347 Name: "system-bar", 348 }, 349 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 350 Subjects: []flowcontrol.Subject{{ 351 Kind: flowcontrol.SubjectKindUser, 352 User: &flowcontrol.UserSubject{Name: "noxu"}, 353 }}, 354 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 355 Verbs: []string{flowcontrol.VerbAll, "create"}, 356 APIGroups: []string{flowcontrol.APIGroupAll, "tak"}, 357 Resources: []string{flowcontrol.ResourceAll, "tok"}, 358 Namespaces: []string{flowcontrol.NamespaceEvery}, 359 }}, 360 }}, 361 }, 362 }, 363 expectedErrors: field.ErrorList{ 364 field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("verbs"), []string{"*", "create"}, "if '*' is present, must not specify other verbs"), 365 field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("apiGroups"), []string{"*", "tak"}, "if '*' is present, must not specify other api groups"), 366 field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("resources"), []string{"*", "tok"}, "if '*' is present, must not specify other resources"), 367 }, 368 }, { 369 name: "flow-schema has both resource rules and non-resource rules should work", 370 flowSchema: &flowcontrol.FlowSchema{ 371 ObjectMeta: metav1.ObjectMeta{ 372 Name: "system-foo", 373 }, 374 Spec: flowcontrol.FlowSchemaSpec{ 375 MatchingPrecedence: 50, 376 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 377 Name: "system-bar", 378 }, 379 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 380 Subjects: []flowcontrol.Subject{{ 381 Kind: flowcontrol.SubjectKindUser, 382 User: &flowcontrol.UserSubject{Name: "noxu"}, 383 }}, 384 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 385 Verbs: []string{flowcontrol.VerbAll}, 386 APIGroups: []string{flowcontrol.APIGroupAll}, 387 Resources: []string{flowcontrol.ResourceAll}, 388 Namespaces: []string{flowcontrol.NamespaceEvery}, 389 }}, 390 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 391 Verbs: []string{flowcontrol.VerbAll}, 392 NonResourceURLs: []string{"/apis/*"}, 393 }}, 394 }}, 395 }, 396 }, 397 expectedErrors: field.ErrorList{}, 398 }, { 399 name: "flow-schema mixes * non-resource URLs should fail", 400 flowSchema: &flowcontrol.FlowSchema{ 401 ObjectMeta: metav1.ObjectMeta{ 402 Name: "system-foo", 403 }, 404 Spec: flowcontrol.FlowSchemaSpec{ 405 MatchingPrecedence: 50, 406 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 407 Name: "system-bar", 408 }, 409 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 410 Subjects: []flowcontrol.Subject{{ 411 Kind: flowcontrol.SubjectKindUser, 412 User: &flowcontrol.UserSubject{Name: "noxu"}, 413 }}, 414 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 415 Verbs: []string{"*"}, 416 NonResourceURLs: []string{flowcontrol.NonResourceAll, "tik"}, 417 }}, 418 }}, 419 }, 420 }, 421 expectedErrors: field.ErrorList{ 422 field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("nonResourceRules").Index(0).Child("nonResourceURLs"), []string{"*", "tik"}, "if '*' is present, must not specify other non-resource URLs"), 423 }, 424 }, { 425 name: "invalid subject kind should fail", 426 flowSchema: &flowcontrol.FlowSchema{ 427 ObjectMeta: metav1.ObjectMeta{ 428 Name: "system-foo", 429 }, 430 Spec: flowcontrol.FlowSchemaSpec{ 431 MatchingPrecedence: 50, 432 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 433 Name: "system-bar", 434 }, 435 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 436 Subjects: []flowcontrol.Subject{{ 437 Kind: "FooKind", 438 }}, 439 NonResourceRules: []flowcontrol.NonResourcePolicyRule{{ 440 Verbs: []string{"*"}, 441 NonResourceURLs: []string{flowcontrol.NonResourceAll}, 442 }}, 443 }}, 444 }, 445 }, 446 expectedErrors: field.ErrorList{ 447 field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), flowcontrol.SubjectKind("FooKind"), supportedSubjectKinds.List()), 448 }, 449 }, { 450 name: "flow-schema w/ invalid verb should fail", 451 flowSchema: &flowcontrol.FlowSchema{ 452 ObjectMeta: metav1.ObjectMeta{ 453 Name: "system-foo", 454 }, 455 Spec: flowcontrol.FlowSchemaSpec{ 456 MatchingPrecedence: 50, 457 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 458 Name: "system-bar", 459 }, 460 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 461 Subjects: []flowcontrol.Subject{{ 462 Kind: flowcontrol.SubjectKindUser, 463 User: &flowcontrol.UserSubject{Name: "noxu"}, 464 }}, 465 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 466 Verbs: []string{"feed"}, 467 APIGroups: []string{flowcontrol.APIGroupAll}, 468 Resources: []string{flowcontrol.ResourceAll}, 469 Namespaces: []string{flowcontrol.NamespaceEvery}, 470 }}, 471 }}, 472 }, 473 }, 474 expectedErrors: field.ErrorList{ 475 field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("verbs"), []string{"feed"}, supportedVerbs.List()), 476 }, 477 }, { 478 name: "flow-schema w/ invalid priority level configuration name should fail", 479 flowSchema: &flowcontrol.FlowSchema{ 480 ObjectMeta: metav1.ObjectMeta{ 481 Name: "system-foo", 482 }, 483 Spec: flowcontrol.FlowSchemaSpec{ 484 MatchingPrecedence: 50, 485 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 486 Name: "system+++$$", 487 }, 488 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 489 Subjects: []flowcontrol.Subject{{ 490 Kind: flowcontrol.SubjectKindUser, 491 User: &flowcontrol.UserSubject{Name: "noxu"}, 492 }}, 493 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 494 Verbs: []string{flowcontrol.VerbAll}, 495 APIGroups: []string{flowcontrol.APIGroupAll}, 496 Resources: []string{flowcontrol.ResourceAll}, 497 Namespaces: []string{flowcontrol.NamespaceEvery}, 498 }}, 499 }}, 500 }, 501 }, 502 expectedErrors: field.ErrorList{ 503 field.Invalid(field.NewPath("spec").Child("priorityLevelConfiguration").Child("name"), "system+++$$", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), 504 }, 505 }, { 506 name: "flow-schema w/ service-account kind missing namespace should fail", 507 flowSchema: &flowcontrol.FlowSchema{ 508 ObjectMeta: metav1.ObjectMeta{ 509 Name: "system-foo", 510 }, 511 Spec: flowcontrol.FlowSchemaSpec{ 512 MatchingPrecedence: 50, 513 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 514 Name: "system-bar", 515 }, 516 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 517 Subjects: []flowcontrol.Subject{{ 518 Kind: flowcontrol.SubjectKindServiceAccount, 519 ServiceAccount: &flowcontrol.ServiceAccountSubject{ 520 Name: "noxu", 521 }, 522 }}, 523 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 524 Verbs: []string{flowcontrol.VerbAll}, 525 APIGroups: []string{flowcontrol.APIGroupAll}, 526 Resources: []string{flowcontrol.ResourceAll}, 527 Namespaces: []string{flowcontrol.NamespaceEvery}, 528 }}, 529 }}, 530 }, 531 }, 532 expectedErrors: field.ErrorList{ 533 field.Required(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("serviceAccount").Child("namespace"), "must specify namespace for service account"), 534 }, 535 }, { 536 name: "flow-schema missing kind should fail", 537 flowSchema: &flowcontrol.FlowSchema{ 538 ObjectMeta: metav1.ObjectMeta{ 539 Name: "system-foo", 540 }, 541 Spec: flowcontrol.FlowSchemaSpec{ 542 MatchingPrecedence: 50, 543 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 544 Name: "system-bar", 545 }, 546 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 547 Subjects: []flowcontrol.Subject{{ 548 Kind: "", 549 }}, 550 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 551 Verbs: []string{flowcontrol.VerbAll}, 552 APIGroups: []string{flowcontrol.APIGroupAll}, 553 Resources: []string{flowcontrol.ResourceAll}, 554 Namespaces: []string{flowcontrol.NamespaceEvery}, 555 }}, 556 }}, 557 }, 558 }, 559 expectedErrors: field.ErrorList{ 560 field.NotSupported(field.NewPath("spec").Child("rules").Index(0).Child("subjects").Index(0).Child("kind"), flowcontrol.SubjectKind(""), supportedSubjectKinds.List()), 561 }, 562 }, { 563 name: "Omitted ResourceRule.Namespaces should fail", 564 flowSchema: &flowcontrol.FlowSchema{ 565 ObjectMeta: metav1.ObjectMeta{ 566 Name: "system-foo", 567 }, 568 Spec: flowcontrol.FlowSchemaSpec{ 569 MatchingPrecedence: 50, 570 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 571 Name: "system-bar", 572 }, 573 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 574 Subjects: []flowcontrol.Subject{{ 575 Kind: flowcontrol.SubjectKindUser, 576 User: &flowcontrol.UserSubject{Name: "noxu"}, 577 }}, 578 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 579 Verbs: []string{flowcontrol.VerbAll}, 580 APIGroups: []string{flowcontrol.APIGroupAll}, 581 Resources: []string{flowcontrol.ResourceAll}, 582 Namespaces: nil, 583 }}, 584 }}, 585 }, 586 }, 587 expectedErrors: field.ErrorList{ 588 field.Required(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces"), "resource rules that are not cluster scoped must supply at least one namespace"), 589 }, 590 }, { 591 name: "ClusterScope is allowed, with no Namespaces", 592 flowSchema: &flowcontrol.FlowSchema{ 593 ObjectMeta: metav1.ObjectMeta{ 594 Name: "system-foo", 595 }, 596 Spec: flowcontrol.FlowSchemaSpec{ 597 MatchingPrecedence: 50, 598 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 599 Name: "system-bar", 600 }, 601 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 602 Subjects: []flowcontrol.Subject{{ 603 Kind: flowcontrol.SubjectKindUser, 604 User: &flowcontrol.UserSubject{Name: "noxu"}, 605 }}, 606 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 607 Verbs: []string{flowcontrol.VerbAll}, 608 APIGroups: []string{flowcontrol.APIGroupAll}, 609 Resources: []string{flowcontrol.ResourceAll}, 610 ClusterScope: true, 611 }}, 612 }}, 613 }, 614 }, 615 expectedErrors: field.ErrorList{}, 616 }, { 617 name: "ClusterScope is allowed with NamespaceEvery", 618 flowSchema: &flowcontrol.FlowSchema{ 619 ObjectMeta: metav1.ObjectMeta{ 620 Name: "system-foo", 621 }, 622 Spec: flowcontrol.FlowSchemaSpec{ 623 MatchingPrecedence: 50, 624 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 625 Name: "system-bar", 626 }, 627 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 628 Subjects: []flowcontrol.Subject{{ 629 Kind: flowcontrol.SubjectKindUser, 630 User: &flowcontrol.UserSubject{Name: "noxu"}, 631 }}, 632 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 633 Verbs: []string{flowcontrol.VerbAll}, 634 APIGroups: []string{flowcontrol.APIGroupAll}, 635 Resources: []string{flowcontrol.ResourceAll}, 636 ClusterScope: true, 637 Namespaces: []string{flowcontrol.NamespaceEvery}, 638 }}, 639 }}, 640 }, 641 }, 642 expectedErrors: field.ErrorList{}, 643 }, { 644 name: "NamespaceEvery may not be combined with particulars", 645 flowSchema: &flowcontrol.FlowSchema{ 646 ObjectMeta: metav1.ObjectMeta{ 647 Name: "system-foo", 648 }, 649 Spec: flowcontrol.FlowSchemaSpec{ 650 MatchingPrecedence: 50, 651 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 652 Name: "system-bar", 653 }, 654 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 655 Subjects: []flowcontrol.Subject{{ 656 Kind: flowcontrol.SubjectKindUser, 657 User: &flowcontrol.UserSubject{Name: "noxu"}, 658 }}, 659 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 660 Verbs: []string{flowcontrol.VerbAll}, 661 APIGroups: []string{flowcontrol.APIGroupAll}, 662 Resources: []string{flowcontrol.ResourceAll}, 663 Namespaces: []string{"foo", flowcontrol.NamespaceEvery}, 664 }}, 665 }}, 666 }, 667 }, 668 expectedErrors: field.ErrorList{ 669 field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces"), []string{"foo", flowcontrol.NamespaceEvery}, "if '*' is present, must not specify other namespaces"), 670 }, 671 }, { 672 name: "ResourceRule.Namespaces must be well formed", 673 flowSchema: &flowcontrol.FlowSchema{ 674 ObjectMeta: metav1.ObjectMeta{ 675 Name: "system-foo", 676 }, 677 Spec: flowcontrol.FlowSchemaSpec{ 678 MatchingPrecedence: 50, 679 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 680 Name: "system-bar", 681 }, 682 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 683 Subjects: []flowcontrol.Subject{{ 684 Kind: flowcontrol.SubjectKindUser, 685 User: &flowcontrol.UserSubject{Name: "noxu"}, 686 }}, 687 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 688 Verbs: []string{flowcontrol.VerbAll}, 689 APIGroups: []string{flowcontrol.APIGroupAll}, 690 Resources: []string{flowcontrol.ResourceAll}, 691 Namespaces: []string{"-foo"}, 692 }}, 693 }}, 694 }, 695 }, 696 expectedErrors: field.ErrorList{ 697 field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("resourceRules").Index(0).Child("namespaces").Index(0), "-foo", nsErrIntro+`a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')`), 698 }, 699 }, { 700 name: "MatchingPrecedence must not be greater than 10000", 701 flowSchema: &flowcontrol.FlowSchema{ 702 ObjectMeta: metav1.ObjectMeta{ 703 Name: "system-foo", 704 }, 705 Spec: flowcontrol.FlowSchemaSpec{ 706 MatchingPrecedence: 10001, 707 PriorityLevelConfiguration: flowcontrol.PriorityLevelConfigurationReference{ 708 Name: "system-bar", 709 }, 710 Rules: []flowcontrol.PolicyRulesWithSubjects{{ 711 Subjects: []flowcontrol.Subject{{ 712 Kind: flowcontrol.SubjectKindUser, 713 User: &flowcontrol.UserSubject{Name: "noxu"}, 714 }}, 715 ResourceRules: []flowcontrol.ResourcePolicyRule{{ 716 Verbs: []string{flowcontrol.VerbAll}, 717 APIGroups: []string{flowcontrol.APIGroupAll}, 718 Resources: []string{flowcontrol.ResourceAll}, 719 Namespaces: []string{flowcontrol.NamespaceEvery}, 720 }}, 721 }}, 722 }, 723 }, 724 expectedErrors: field.ErrorList{ 725 field.Invalid(field.NewPath("spec").Child("matchingPrecedence"), int32(10001), "must not be greater than 10000"), 726 }, 727 }} 728 for _, testCase := range testCases { 729 t.Run(testCase.name, func(t *testing.T) { 730 errs := ValidateFlowSchema(testCase.flowSchema) 731 if !assert.ElementsMatch(t, testCase.expectedErrors, errs) { 732 t.Logf("mismatch: %v", cmp.Diff(testCase.expectedErrors, errs)) 733 } 734 }) 735 } 736 } 737 738 func TestPriorityLevelConfigurationValidation(t *testing.T) { 739 badSpec := flowcontrol.PriorityLevelConfigurationSpec{ 740 Type: flowcontrol.PriorityLevelEnablementLimited, 741 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 742 NominalConcurrencyShares: 42, 743 LimitResponse: flowcontrol.LimitResponse{ 744 Type: flowcontrol.LimitResponseTypeReject}, 745 }, 746 } 747 748 badExemptSpec1 := flowcontrol.PriorityLevelConfigurationSpec{ 749 Type: flowcontrol.PriorityLevelEnablementExempt, 750 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{ 751 NominalConcurrencyShares: pointer.Int32(-1), 752 LendablePercent: pointer.Int32(101), 753 }, 754 } 755 badExemptSpec2 := flowcontrol.PriorityLevelConfigurationSpec{ 756 Type: flowcontrol.PriorityLevelEnablementExempt, 757 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{ 758 NominalConcurrencyShares: pointer.Int32(-1), 759 LendablePercent: pointer.Int32(-1), 760 }, 761 } 762 763 badExemptSpec3 := flowcontrol.PriorityLevelConfigurationSpec{ 764 Type: flowcontrol.PriorityLevelEnablementExempt, 765 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{}, 766 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 767 NominalConcurrencyShares: 42, 768 LimitResponse: flowcontrol.LimitResponse{ 769 Type: flowcontrol.LimitResponseTypeReject}, 770 }, 771 } 772 773 validChangesInExemptFieldOfExemptPLFn := func() flowcontrol.PriorityLevelConfigurationSpec { 774 have, _ := internalbootstrap.MandatoryPriorityLevelConfigurations[flowcontrol.PriorityLevelConfigurationNameExempt] 775 return flowcontrol.PriorityLevelConfigurationSpec{ 776 Type: flowcontrol.PriorityLevelEnablementExempt, 777 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{ 778 NominalConcurrencyShares: pointer.Int32(*have.Spec.Exempt.NominalConcurrencyShares + 10), 779 LendablePercent: pointer.Int32(*have.Spec.Exempt.LendablePercent + 10), 780 }, 781 } 782 } 783 784 exemptTypeRepurposed := &flowcontrol.PriorityLevelConfiguration{ 785 ObjectMeta: metav1.ObjectMeta{ 786 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 787 }, 788 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 789 // changing the type from exempt to limited 790 Type: flowcontrol.PriorityLevelEnablementLimited, 791 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{}, 792 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 793 NominalConcurrencyShares: 42, 794 LimitResponse: flowcontrol.LimitResponse{ 795 Type: flowcontrol.LimitResponseTypeReject}, 796 }, 797 }, 798 } 799 800 testCases := []struct { 801 name string 802 priorityLevelConfiguration *flowcontrol.PriorityLevelConfiguration 803 requestGV *schema.GroupVersion 804 expectedErrors field.ErrorList 805 }{{ 806 name: "exempt should work", 807 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 808 ObjectMeta: metav1.ObjectMeta{ 809 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 810 }, 811 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 812 Type: flowcontrol.PriorityLevelEnablementExempt, 813 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{ 814 NominalConcurrencyShares: pointer.Int32(0), 815 LendablePercent: pointer.Int32(0), 816 }, 817 }, 818 }, 819 expectedErrors: field.ErrorList{}, 820 }, { 821 name: "wrong exempt spec should fail", 822 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 823 ObjectMeta: metav1.ObjectMeta{ 824 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 825 }, 826 Spec: badSpec, 827 }, 828 expectedErrors: field.ErrorList{ 829 field.Invalid(field.NewPath("spec").Child("type"), flowcontrol.PriorityLevelEnablementLimited, "type must be 'Exempt' if and only if name is 'exempt'"), 830 field.Invalid(field.NewPath("spec"), badSpec, "spec of 'exempt' except the 'spec.exempt' field must equal the fixed value"), 831 }, 832 }, { 833 name: "exempt priority level should have appropriate values for Exempt field", 834 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 835 ObjectMeta: metav1.ObjectMeta{ 836 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 837 }, 838 Spec: badExemptSpec1, 839 }, 840 expectedErrors: field.ErrorList{ 841 field.Invalid(field.NewPath("spec").Child("exempt").Child("nominalConcurrencyShares"), int32(-1), "must be a non-negative integer"), 842 field.Invalid(field.NewPath("spec").Child("exempt").Child("lendablePercent"), int32(101), "must be between 0 and 100, inclusive"), 843 }, 844 }, { 845 name: "exempt priority level should have appropriate values for Exempt field", 846 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 847 ObjectMeta: metav1.ObjectMeta{ 848 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 849 }, 850 Spec: badExemptSpec2, 851 }, 852 expectedErrors: field.ErrorList{ 853 field.Invalid(field.NewPath("spec").Child("exempt").Child("nominalConcurrencyShares"), int32(-1), "must be a non-negative integer"), 854 field.Invalid(field.NewPath("spec").Child("exempt").Child("lendablePercent"), int32(-1), "must be between 0 and 100, inclusive"), 855 }, 856 }, { 857 name: "admins are not allowed to repurpose the 'exempt' pl to a limited type", 858 priorityLevelConfiguration: exemptTypeRepurposed, 859 expectedErrors: field.ErrorList{ 860 field.Invalid(field.NewPath("spec").Child("type"), flowcontrol.PriorityLevelEnablementLimited, "type must be 'Exempt' if and only if name is 'exempt'"), 861 field.Forbidden(field.NewPath("spec").Child("exempt"), "must be nil if the type is Limited"), 862 field.Invalid(field.NewPath("spec"), exemptTypeRepurposed.Spec, "spec of 'exempt' except the 'spec.exempt' field must equal the fixed value"), 863 }, 864 }, { 865 name: "admins are not allowed to change any field of the 'exempt' pl except 'Exempt'", 866 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 867 ObjectMeta: metav1.ObjectMeta{ 868 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 869 }, 870 Spec: badExemptSpec3, 871 }, 872 expectedErrors: field.ErrorList{ 873 field.Invalid(field.NewPath("spec"), badExemptSpec3, "spec of 'exempt' except the 'spec.exempt' field must equal the fixed value"), 874 field.Forbidden(field.NewPath("spec").Child("limited"), "must be nil if the type is not Limited"), 875 }, 876 }, { 877 name: "admins are allowed to change the Exempt field of the 'exempt' pl", 878 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 879 ObjectMeta: metav1.ObjectMeta{ 880 Name: flowcontrol.PriorityLevelConfigurationNameExempt, 881 }, 882 Spec: validChangesInExemptFieldOfExemptPLFn(), 883 }, 884 expectedErrors: field.ErrorList{}, 885 }, { 886 name: "limited must not set exempt priority level configuration for borrowing", 887 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 888 ObjectMeta: metav1.ObjectMeta{ 889 Name: "broken-limited", 890 }, 891 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 892 Type: flowcontrol.PriorityLevelEnablementLimited, 893 Exempt: &flowcontrol.ExemptPriorityLevelConfiguration{}, 894 }, 895 }, 896 expectedErrors: field.ErrorList{ 897 field.Forbidden(field.NewPath("spec").Child("exempt"), "must be nil if the type is Limited"), 898 field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited"), 899 }, 900 }, { 901 name: "limited requires more details", 902 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 903 ObjectMeta: metav1.ObjectMeta{ 904 Name: "broken-limited", 905 }, 906 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 907 Type: flowcontrol.PriorityLevelEnablementLimited, 908 }, 909 }, 910 expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited"), "must not be empty when type is Limited")}, 911 }, { 912 name: "max-in-flight should work", 913 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 914 ObjectMeta: metav1.ObjectMeta{ 915 Name: "max-in-flight", 916 }, 917 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 918 Type: flowcontrol.PriorityLevelEnablementLimited, 919 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 920 NominalConcurrencyShares: 42, 921 LimitResponse: flowcontrol.LimitResponse{ 922 Type: flowcontrol.LimitResponseTypeReject}, 923 }, 924 }, 925 }, 926 expectedErrors: field.ErrorList{}, 927 }, { 928 name: "forbid queuing details when not queuing", 929 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 930 ObjectMeta: metav1.ObjectMeta{ 931 Name: "system-foo", 932 }, 933 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 934 Type: flowcontrol.PriorityLevelEnablementLimited, 935 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 936 NominalConcurrencyShares: 100, 937 LimitResponse: flowcontrol.LimitResponse{ 938 Type: flowcontrol.LimitResponseTypeReject, 939 Queuing: &flowcontrol.QueuingConfiguration{ 940 Queues: 512, 941 HandSize: 4, 942 QueueLengthLimit: 100, 943 }}}}, 944 }, 945 expectedErrors: field.ErrorList{field.Forbidden(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must be nil if limited.limitResponse.type is not Limited")}, 946 }, { 947 name: "wrong backstop spec should fail", 948 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 949 ObjectMeta: metav1.ObjectMeta{ 950 Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, 951 }, 952 Spec: badSpec, 953 }, 954 expectedErrors: field.ErrorList{field.Invalid(field.NewPath("spec"), badSpec, "spec of 'catch-all' must equal the fixed value")}, 955 }, { 956 name: "backstop should work", 957 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 958 ObjectMeta: metav1.ObjectMeta{ 959 Name: flowcontrol.PriorityLevelConfigurationNameCatchAll, 960 }, 961 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 962 Type: flowcontrol.PriorityLevelEnablementLimited, 963 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 964 NominalConcurrencyShares: 5, 965 LendablePercent: pointer.Int32(0), 966 LimitResponse: flowcontrol.LimitResponse{ 967 Type: flowcontrol.LimitResponseTypeReject, 968 }}}, 969 }, 970 expectedErrors: field.ErrorList{}, 971 }, { 972 name: "broken queuing level should fail", 973 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 974 ObjectMeta: metav1.ObjectMeta{ 975 Name: "system-foo", 976 }, 977 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 978 Type: flowcontrol.PriorityLevelEnablementLimited, 979 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 980 NominalConcurrencyShares: 100, 981 LimitResponse: flowcontrol.LimitResponse{ 982 Type: flowcontrol.LimitResponseTypeQueue, 983 }}}, 984 }, 985 expectedErrors: field.ErrorList{field.Required(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing"), "must not be empty if limited.limitResponse.type is Limited")}, 986 }, { 987 name: "normal customized priority level should work", 988 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 989 ObjectMeta: metav1.ObjectMeta{ 990 Name: "system-foo", 991 }, 992 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 993 Type: flowcontrol.PriorityLevelEnablementLimited, 994 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 995 NominalConcurrencyShares: 100, 996 LimitResponse: flowcontrol.LimitResponse{ 997 Type: flowcontrol.LimitResponseTypeQueue, 998 Queuing: &flowcontrol.QueuingConfiguration{ 999 Queues: 512, 1000 HandSize: 4, 1001 QueueLengthLimit: 100, 1002 }}}}, 1003 }, 1004 expectedErrors: field.ErrorList{}, 1005 }, { 1006 name: "customized priority level w/ overflowing handSize/queues should fail 1", 1007 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 1008 ObjectMeta: metav1.ObjectMeta{ 1009 Name: "system-foo", 1010 }, 1011 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 1012 Type: flowcontrol.PriorityLevelEnablementLimited, 1013 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 1014 NominalConcurrencyShares: 100, 1015 LimitResponse: flowcontrol.LimitResponse{ 1016 Type: flowcontrol.LimitResponseTypeQueue, 1017 Queuing: &flowcontrol.QueuingConfiguration{ 1018 QueueLengthLimit: 100, 1019 Queues: 512, 1020 HandSize: 8, 1021 }}}}, 1022 }, 1023 expectedErrors: field.ErrorList{ 1024 field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(8), "required entropy bits of deckSize 512 and handSize 8 should not be greater than 60"), 1025 }, 1026 }, { 1027 name: "customized priority level w/ overflowing handSize/queues should fail 2", 1028 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 1029 ObjectMeta: metav1.ObjectMeta{ 1030 Name: "system-foo", 1031 }, 1032 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 1033 Type: flowcontrol.PriorityLevelEnablementLimited, 1034 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 1035 NominalConcurrencyShares: 100, 1036 LimitResponse: flowcontrol.LimitResponse{ 1037 Type: flowcontrol.LimitResponseTypeQueue, 1038 Queuing: &flowcontrol.QueuingConfiguration{ 1039 QueueLengthLimit: 100, 1040 Queues: 128, 1041 HandSize: 10, 1042 }}}}, 1043 }, 1044 expectedErrors: field.ErrorList{ 1045 field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(10), "required entropy bits of deckSize 128 and handSize 10 should not be greater than 60"), 1046 }, 1047 }, { 1048 name: "customized priority level w/ overflowing handSize/queues should fail 3", 1049 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 1050 ObjectMeta: metav1.ObjectMeta{ 1051 Name: "system-foo", 1052 }, 1053 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 1054 Type: flowcontrol.PriorityLevelEnablementLimited, 1055 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 1056 NominalConcurrencyShares: 100, 1057 LimitResponse: flowcontrol.LimitResponse{ 1058 Type: flowcontrol.LimitResponseTypeQueue, 1059 Queuing: &flowcontrol.QueuingConfiguration{ 1060 QueueLengthLimit: 100, 1061 Queues: math.MaxInt32, 1062 HandSize: 3, 1063 }}}}, 1064 }, 1065 expectedErrors: field.ErrorList{ 1066 field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(3), "required entropy bits of deckSize 2147483647 and handSize 3 should not be greater than 60"), 1067 field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("queues"), int32(math.MaxInt32), "must not be greater than 10000000"), 1068 }, 1069 }, { 1070 name: "customized priority level w/ handSize=2 and queues=10^7 should work", 1071 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 1072 ObjectMeta: metav1.ObjectMeta{ 1073 Name: "system-foo", 1074 }, 1075 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 1076 Type: flowcontrol.PriorityLevelEnablementLimited, 1077 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 1078 NominalConcurrencyShares: 100, 1079 LimitResponse: flowcontrol.LimitResponse{ 1080 Type: flowcontrol.LimitResponseTypeQueue, 1081 Queuing: &flowcontrol.QueuingConfiguration{ 1082 QueueLengthLimit: 100, 1083 Queues: 10 * 1000 * 1000, // 10^7 1084 HandSize: 2, 1085 }}}}, 1086 }, 1087 expectedErrors: field.ErrorList{}, 1088 }, { 1089 name: "customized priority level w/ handSize greater than queues should fail", 1090 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 1091 ObjectMeta: metav1.ObjectMeta{ 1092 Name: "system-foo", 1093 }, 1094 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 1095 Type: flowcontrol.PriorityLevelEnablementLimited, 1096 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 1097 NominalConcurrencyShares: 100, 1098 LimitResponse: flowcontrol.LimitResponse{ 1099 Type: flowcontrol.LimitResponseTypeQueue, 1100 Queuing: &flowcontrol.QueuingConfiguration{ 1101 QueueLengthLimit: 100, 1102 Queues: 7, 1103 HandSize: 8, 1104 }}}}, 1105 }, 1106 expectedErrors: field.ErrorList{ 1107 field.Invalid(field.NewPath("spec").Child("limited").Child("limitResponse").Child("queuing").Child("handSize"), int32(8), "should not be greater than queues (7)"), 1108 }, 1109 }, { 1110 name: "the roundtrip annotation is forbidden", 1111 priorityLevelConfiguration: &flowcontrol.PriorityLevelConfiguration{ 1112 ObjectMeta: metav1.ObjectMeta{ 1113 Name: "with-forbidden-annotation", 1114 Annotations: map[string]string{ 1115 flowcontrolv1beta3.PriorityLevelPreserveZeroConcurrencySharesKey: "", 1116 }, 1117 }, 1118 Spec: flowcontrol.PriorityLevelConfigurationSpec{ 1119 Type: flowcontrol.PriorityLevelEnablementLimited, 1120 Limited: &flowcontrol.LimitedPriorityLevelConfiguration{ 1121 NominalConcurrencyShares: 42, 1122 LimitResponse: flowcontrol.LimitResponse{ 1123 Type: flowcontrol.LimitResponseTypeReject}, 1124 }, 1125 }, 1126 }, 1127 // the internal object should never have the round trip annotation 1128 requestGV: &schema.GroupVersion{}, 1129 expectedErrors: field.ErrorList{ 1130 field.Forbidden(field.NewPath("metadata").Child("annotations"), fmt.Sprintf("annotation '%s' is forbidden", flowcontrolv1beta3.PriorityLevelPreserveZeroConcurrencySharesKey)), 1131 }, 1132 }} 1133 for _, testCase := range testCases { 1134 t.Run(testCase.name, func(t *testing.T) { 1135 gv := flowcontrolv1beta3.SchemeGroupVersion 1136 if testCase.requestGV != nil { 1137 gv = *testCase.requestGV 1138 } 1139 errs := ValidatePriorityLevelConfiguration(testCase.priorityLevelConfiguration, gv, PriorityLevelValidationOptions{}) 1140 if !assert.ElementsMatch(t, testCase.expectedErrors, errs) { 1141 t.Logf("mismatch: %v", cmp.Diff(testCase.expectedErrors, errs)) 1142 } 1143 }) 1144 } 1145 } 1146 1147 func TestValidateFlowSchemaStatus(t *testing.T) { 1148 testCases := []struct { 1149 name string 1150 status *flowcontrol.FlowSchemaStatus 1151 expectedErrors field.ErrorList 1152 }{{ 1153 name: "empty status should work", 1154 status: &flowcontrol.FlowSchemaStatus{}, 1155 expectedErrors: field.ErrorList{}, 1156 }, { 1157 name: "duplicate key should fail", 1158 status: &flowcontrol.FlowSchemaStatus{ 1159 Conditions: []flowcontrol.FlowSchemaCondition{{ 1160 Type: "1", 1161 }, { 1162 Type: "1", 1163 }}, 1164 }, 1165 expectedErrors: field.ErrorList{ 1166 field.Duplicate(field.NewPath("status").Child("conditions").Index(1).Child("type"), flowcontrol.FlowSchemaConditionType("1")), 1167 }, 1168 }, { 1169 name: "missing key should fail", 1170 status: &flowcontrol.FlowSchemaStatus{ 1171 Conditions: []flowcontrol.FlowSchemaCondition{{ 1172 Type: "", 1173 }}, 1174 }, 1175 expectedErrors: field.ErrorList{ 1176 field.Required(field.NewPath("status").Child("conditions").Index(0).Child("type"), "must not be empty"), 1177 }, 1178 }} 1179 for _, testCase := range testCases { 1180 t.Run(testCase.name, func(t *testing.T) { 1181 errs := ValidateFlowSchemaStatus(testCase.status, field.NewPath("status")) 1182 if !assert.ElementsMatch(t, testCase.expectedErrors, errs) { 1183 t.Logf("mismatch: %v", cmp.Diff(testCase.expectedErrors, errs)) 1184 } 1185 }) 1186 } 1187 } 1188 1189 func TestValidatePriorityLevelConfigurationStatus(t *testing.T) { 1190 testCases := []struct { 1191 name string 1192 status *flowcontrol.PriorityLevelConfigurationStatus 1193 expectedErrors field.ErrorList 1194 }{{ 1195 name: "empty status should work", 1196 status: &flowcontrol.PriorityLevelConfigurationStatus{}, 1197 expectedErrors: field.ErrorList{}, 1198 }, { 1199 name: "duplicate key should fail", 1200 status: &flowcontrol.PriorityLevelConfigurationStatus{ 1201 Conditions: []flowcontrol.PriorityLevelConfigurationCondition{{ 1202 Type: "1", 1203 }, { 1204 Type: "1", 1205 }}, 1206 }, 1207 expectedErrors: field.ErrorList{ 1208 field.Duplicate(field.NewPath("status").Child("conditions").Index(1).Child("type"), flowcontrol.PriorityLevelConfigurationConditionType("1")), 1209 }, 1210 }, { 1211 name: "missing key should fail", 1212 status: &flowcontrol.PriorityLevelConfigurationStatus{ 1213 Conditions: []flowcontrol.PriorityLevelConfigurationCondition{{ 1214 Type: "", 1215 }}, 1216 }, 1217 expectedErrors: field.ErrorList{ 1218 field.Required(field.NewPath("status").Child("conditions").Index(0).Child("type"), "must not be empty"), 1219 }, 1220 }} 1221 for _, testCase := range testCases { 1222 t.Run(testCase.name, func(t *testing.T) { 1223 errs := ValidatePriorityLevelConfigurationStatus(testCase.status, field.NewPath("status")) 1224 if !assert.ElementsMatch(t, testCase.expectedErrors, errs) { 1225 t.Logf("mismatch: %v", cmp.Diff(testCase.expectedErrors, errs)) 1226 } 1227 }) 1228 } 1229 } 1230 1231 func TestValidateNonResourceURLPath(t *testing.T) { 1232 testCases := []struct { 1233 name string 1234 path string 1235 expectingError bool 1236 }{{ 1237 name: "empty string should fail", 1238 path: "", 1239 expectingError: true, 1240 }, { 1241 name: "no slash should fail", 1242 path: "foo", 1243 expectingError: true, 1244 }, { 1245 name: "single slash should work", 1246 path: "/", 1247 expectingError: false, 1248 }, { 1249 name: "continuous slash should fail", 1250 path: "//", 1251 expectingError: true, 1252 }, { 1253 name: "/foo slash should work", 1254 path: "/foo", 1255 expectingError: false, 1256 }, { 1257 name: "multiple continuous slashes should fail", 1258 path: "/////", 1259 expectingError: true, 1260 }, { 1261 name: "ending up with slash should work", 1262 path: "/apis/", 1263 expectingError: false, 1264 }, { 1265 name: "ending up with wildcard should work", 1266 path: "/healthz/*", 1267 expectingError: false, 1268 }, { 1269 name: "single wildcard inside the path should fail", 1270 path: "/healthz/*/foo", 1271 expectingError: true, 1272 }, { 1273 name: "white-space in the path should fail", 1274 path: "/healthz/foo bar", 1275 expectingError: true, 1276 }, { 1277 name: "wildcard plus plain path should fail", 1278 path: "/health*", 1279 expectingError: true, 1280 }, { 1281 name: "wildcard plus plain path should fail 2", 1282 path: "/health*/foo", 1283 expectingError: true, 1284 }, { 1285 name: "multiple wildcard internal and suffix should fail", 1286 path: "/*/*", 1287 expectingError: true, 1288 }} 1289 for _, testCase := range testCases { 1290 t.Run(testCase.name, func(t *testing.T) { 1291 err := ValidateNonResourceURLPath(testCase.path, field.NewPath("")) 1292 assert.Equal(t, testCase.expectingError, err != nil, 1293 "actual error: %v", err) 1294 }) 1295 } 1296 } 1297 1298 func TestValidateLimitedPriorityLevelConfiguration(t *testing.T) { 1299 errExpectedFn := func(fieldName string, v int32, msg string) field.ErrorList { 1300 return field.ErrorList{ 1301 field.Invalid(field.NewPath("spec").Child("limited").Child(fieldName), int32(v), msg), 1302 } 1303 } 1304 1305 tests := []struct { 1306 requestVersion schema.GroupVersion 1307 allowZero bool 1308 concurrencyShares int32 1309 errExpected field.ErrorList 1310 }{{ 1311 requestVersion: flowcontrolv1beta1.SchemeGroupVersion, 1312 concurrencyShares: 0, 1313 errExpected: errExpectedFn("assuredConcurrencyShares", 0, "must be positive"), 1314 }, { 1315 requestVersion: flowcontrolv1beta2.SchemeGroupVersion, 1316 concurrencyShares: 0, 1317 errExpected: errExpectedFn("assuredConcurrencyShares", 0, "must be positive"), 1318 }, { 1319 requestVersion: flowcontrolv1beta3.SchemeGroupVersion, 1320 concurrencyShares: 0, 1321 errExpected: errExpectedFn("nominalConcurrencyShares", 0, "must be positive"), 1322 }, { 1323 requestVersion: flowcontrolv1.SchemeGroupVersion, 1324 concurrencyShares: 0, 1325 errExpected: errExpectedFn("nominalConcurrencyShares", 0, "must be positive"), 1326 }, { 1327 requestVersion: flowcontrolv1beta3.SchemeGroupVersion, 1328 concurrencyShares: 100, 1329 errExpected: nil, 1330 }, { 1331 requestVersion: flowcontrolv1beta3.SchemeGroupVersion, 1332 allowZero: true, 1333 concurrencyShares: 0, 1334 errExpected: nil, 1335 }, { 1336 requestVersion: flowcontrolv1beta3.SchemeGroupVersion, 1337 allowZero: true, 1338 concurrencyShares: -1, 1339 errExpected: errExpectedFn("nominalConcurrencyShares", -1, "must be a non-negative integer"), 1340 }, { 1341 requestVersion: flowcontrolv1beta3.SchemeGroupVersion, 1342 allowZero: true, 1343 concurrencyShares: 1, 1344 errExpected: nil, 1345 }, { 1346 requestVersion: flowcontrolv1.SchemeGroupVersion, 1347 allowZero: true, 1348 concurrencyShares: 0, 1349 errExpected: nil, 1350 }, { 1351 requestVersion: flowcontrolv1.SchemeGroupVersion, 1352 allowZero: true, 1353 concurrencyShares: -1, 1354 errExpected: errExpectedFn("nominalConcurrencyShares", -1, "must be a non-negative integer"), 1355 }, { 1356 requestVersion: flowcontrolv1.SchemeGroupVersion, 1357 allowZero: true, 1358 concurrencyShares: 1, 1359 errExpected: nil, 1360 }, { 1361 // this should never really happen in real life, the request 1362 // context should always contain the request {group, version} 1363 requestVersion: schema.GroupVersion{}, 1364 concurrencyShares: 0, 1365 errExpected: errExpectedFn("nominalConcurrencyShares", 0, "must be positive"), 1366 }} 1367 1368 for _, test := range tests { 1369 t.Run(test.requestVersion.String(), func(t *testing.T) { 1370 configuration := &flowcontrol.LimitedPriorityLevelConfiguration{ 1371 NominalConcurrencyShares: test.concurrencyShares, 1372 LimitResponse: flowcontrol.LimitResponse{ 1373 Type: flowcontrol.LimitResponseTypeReject, 1374 }, 1375 } 1376 specPath := field.NewPath("spec").Child("limited") 1377 1378 errGot := ValidateLimitedPriorityLevelConfiguration(configuration, test.requestVersion, specPath, PriorityLevelValidationOptions{AllowZeroLimitedNominalConcurrencyShares: test.allowZero}) 1379 if !cmp.Equal(test.errExpected, errGot) { 1380 t.Errorf("Expected error: %v, diff: %s", test.errExpected, cmp.Diff(test.errExpected, errGot)) 1381 } 1382 }) 1383 } 1384 } 1385 1386 func TestValidateLimitedPriorityLevelConfigurationWithBorrowing(t *testing.T) { 1387 errLendablePercentFn := func(v int32) field.ErrorList { 1388 return field.ErrorList{ 1389 field.Invalid(field.NewPath("spec").Child("limited").Child("lendablePercent"), v, "must be between 0 and 100, inclusive"), 1390 } 1391 } 1392 errBorrowingLimitPercentFn := func(v int32) field.ErrorList { 1393 return field.ErrorList{ 1394 field.Invalid(field.NewPath("spec").Child("limited").Child("borrowingLimitPercent"), v, "if specified, must be a non-negative integer"), 1395 } 1396 } 1397 1398 makeTestNameFn := func(lendablePercent *int32, borrowingLimitPercent *int32) string { 1399 formatFn := func(v *int32) string { 1400 if v == nil { 1401 return "<nil>" 1402 } 1403 return fmt.Sprintf("%d", *v) 1404 } 1405 return fmt.Sprintf("lendablePercent %s, borrowingLimitPercent %s", formatFn(lendablePercent), formatFn(borrowingLimitPercent)) 1406 } 1407 1408 tests := []struct { 1409 lendablePercent *int32 1410 borrowingLimitPercent *int32 1411 errExpected field.ErrorList 1412 }{{ 1413 lendablePercent: nil, 1414 errExpected: nil, 1415 }, { 1416 lendablePercent: pointer.Int32(0), 1417 errExpected: nil, 1418 }, { 1419 lendablePercent: pointer.Int32(100), 1420 errExpected: nil, 1421 }, { 1422 lendablePercent: pointer.Int32(101), 1423 errExpected: errLendablePercentFn(101), 1424 }, { 1425 lendablePercent: pointer.Int32(-1), 1426 errExpected: errLendablePercentFn(-1), 1427 }, { 1428 borrowingLimitPercent: nil, 1429 errExpected: nil, 1430 }, { 1431 borrowingLimitPercent: pointer.Int32(1), 1432 errExpected: nil, 1433 }, { 1434 borrowingLimitPercent: pointer.Int32(100), 1435 errExpected: nil, 1436 }, { 1437 borrowingLimitPercent: pointer.Int32(0), 1438 errExpected: nil, 1439 }, { 1440 borrowingLimitPercent: pointer.Int32(-1), 1441 errExpected: errBorrowingLimitPercentFn(-1), 1442 }} 1443 1444 for _, test := range tests { 1445 t.Run(makeTestNameFn(test.lendablePercent, test.borrowingLimitPercent), func(t *testing.T) { 1446 configuration := &flowcontrol.LimitedPriorityLevelConfiguration{ 1447 NominalConcurrencyShares: 1, 1448 LimitResponse: flowcontrol.LimitResponse{ 1449 Type: flowcontrol.LimitResponseTypeReject, 1450 }, 1451 LendablePercent: test.lendablePercent, 1452 BorrowingLimitPercent: test.borrowingLimitPercent, 1453 } 1454 specPath := field.NewPath("spec").Child("limited") 1455 1456 errGot := ValidateLimitedPriorityLevelConfiguration(configuration, flowcontrolv1.SchemeGroupVersion, specPath, PriorityLevelValidationOptions{}) 1457 if !cmp.Equal(test.errExpected, errGot) { 1458 t.Errorf("Expected error: %v, diff: %s", test.errExpected, cmp.Diff(test.errExpected, errGot)) 1459 } 1460 }) 1461 } 1462 }