1
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
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,
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
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
1362
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 }
1463
View as plain text