...

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

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

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"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  }
  1463  

View as plain text